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=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         end
110
111         -- Function to reopen logfiles
112         function prosody.reopen_logfiles()
113                 log("info", "Re-opening log files");
114                 eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
115                 prosody.events.fire_event("reopen-log-files");
116         end
117
118         -- Function to initiate prosody shutdown
119         function prosody.shutdown(reason)
120                 log("info", "Shutting down: %s", reason or "unknown reason");
121                 prosody.events.fire_event("server-stopping", {reason = reason});
122                 server.setquitting(true);
123         end
124 end
125
126 function read_version()
127         -- Try to determine version
128         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
129         if version_file then
130                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
131                 version_file:close();
132                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
133                         prosody.version = "hg:"..prosody.version;
134                 end
135         else
136                 prosody.version = "unknown";
137         end
138 end
139
140 function load_secondary_libraries()
141         --- Load and initialise core modules
142         require "util.import"
143         require "core.xmlhandlers"
144         require "core.rostermanager"
145         require "core.eventmanager"
146         require "core.hostmanager"
147         require "core.modulemanager"
148         require "core.usermanager"
149         require "core.sessionmanager"
150         require "core.stanza_router"
151
152         require "util.array"
153         require "util.iterators"
154         require "util.timer"
155         require "util.helpers"
156         
157         -- Commented to protect us from 
158         -- the second kind of people
159         --[[ 
160         pcall(require, "remdebug.engine");
161         if remdebug then remdebug.engine.start() end
162         ]]
163
164         require "net.connlisteners";
165         
166         require "util.stanza"
167         require "util.jid"
168 end
169
170 function init_data_store()
171         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
172         require "util.datamanager".set_data_path(data_path);
173         require "util.datamanager".add_callback(function(username, host, datastore, data)
174                 if config.get(host, "core", "anonymous_login") then
175                         return false;
176                 end
177                 return username, host, datastore, data;
178         end);
179 end
180
181 function prepare_to_start()
182         -- Signal to modules that we are ready to start
183         eventmanager.fire_event("server-starting");
184         prosody.events.fire_event("server-starting");
185
186         -- Load SSL settings from config, and create a ctx table
187         local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
188         if global_ssl_ctx then
189                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
190                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
191         end
192
193         local cl = require "net.connlisteners";
194         -- start listening on sockets
195         function net_activate_ports(option, listener, default, conntype)
196                 local ports = config.get("*", "core", option.."_ports") or default;
197                 if type(ports) == "number" then ports = {ports} end;
198                 
199                 if type(ports) ~= "table" then
200                         log("error", "core."..option.." is not a table");
201                 else
202                         for _, port in ipairs(ports) do
203                                 if type(port) ~= "number" then
204                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
205                                 else
206                                         cl.start(listener, { 
207                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
208                                                 port = port,
209                                                 interface = config.get("*", "core", option.."_interface") 
210                                                         or cl.get(listener).default_interface 
211                                                         or config.get("*", "core", "interface"),
212                                                 type = conntype
213                                         });
214                                 end
215                         end
216                 end
217         end
218
219         net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
220         net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
221         net_activate_ports("component", "xmppcomponent", {}, "tcp");
222         net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
223         
224         if cl.get("console") then
225                 cl.start("console", { interface = config.get("*", "core", "console_interface") or "127.0.0.1" })
226         end
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                         for username, user in pairs(host.sessions) do
281                                 for resource, session in pairs(user.sessions) do
282                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
283                                         session:close("system-shutdown");
284                                 end
285                         end
286                 end
287         
288                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
289                 if host.s2sout then
290                         for remotehost, session in pairs(host.s2sout) do
291                                 if session.close then
292                                         session:close("system-shutdown");
293                                 else
294                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
295                                 end
296                         end
297                 end
298         end
299
300         log("info", "Shutdown status: Closing all server connections");
301         server.closeall();
302         
303         server.setquitting(true);
304 end
305
306 read_config();
307 load_libraries();
308 init_global_state();
309 read_version();
310 log("info", "Hello and welcome to Prosody version %s", prosody.version);
311 load_secondary_libraries();
312 init_data_store();
313 prepare_to_start();
314 init_global_protection();
315
316 eventmanager.fire_event("server-started");
317 prosody.events.fire_event("server-started");
318
319 loop();
320
321 log("info", "Shutting down...");
322 cleanup();
323 eventmanager.fire_event("server-stopped");
324 prosody.events.fire_event("server-stopped");
325 log("info", "Shutdown complete");
326