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