bd0bd0883aa0e326c99c1c11f38fe37477991a0c
[prosody.git] / prosody
1 #!/usr/bin/env lua
2 -- Prosody IM v0.4
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 if CFG_DATADIR then
25         if os.getenv("HOME") then
26                 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
27         end
28 end
29
30 -- Required to be able to find packages installed with luarocks
31 pcall(require, "luarocks.require")
32
33
34 config = require "core.configmanager"
35
36 do
37         -- TODO: Check for other formats when we add support for them
38         -- Use lfs? Make a new conf/ dir?
39         local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
40         if not ok then
41                 print("\n");
42                 print("**************************");
43                 if level == "parser" then
44                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
45                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
46                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
47                         print("");
48                 elseif level == "file" then
49                         print("Prosody was unable to find the configuration file.");
50                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
51                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
52                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
53                 end
54                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
55                 print("Good luck!");
56                 print("**************************");
57                 print("");
58                 os.exit(1);
59         end
60 end
61
62 --- Initialize logging
63 require "core.loggingmanager"
64
65 --- Check runtime dependencies
66 require "util.dependencies"
67
68 --- Load socket framework
69 local server = require "net.server"
70
71 bare_sessions = {};
72 full_sessions = {};
73 hosts = {};
74
75 -- Global 'prosody' object
76 prosody = {};
77 local prosody = prosody;
78
79 prosody.bare_sessions = bare_sessions;
80 prosody.full_sessions = full_sessions;
81 prosody.hosts = hosts;
82
83 prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
84                   plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
85
86 prosody.arg = arg;
87
88 --- Load and initialise core modules
89 require "util.import"
90 require "core.xmlhandlers"
91 require "core.rostermanager"
92 require "core.eventmanager"
93 require "core.hostmanager"
94 require "core.modulemanager"
95 require "core.usermanager"
96 require "core.sessionmanager"
97 require "core.stanza_router"
98
99 require "util.array"
100 require "util.iterators"
101 require "util.timer"
102
103 -- Commented to protect us from 
104 -- the second kind of people
105 --[[ 
106 pcall(require, "remdebug.engine");
107 if remdebug then remdebug.engine.start() end
108 ]]
109
110 local cl = require "net.connlisteners";
111
112 require "util.stanza"
113 require "util.jid"
114
115 ------------------------------------------------------------------------
116
117
118 ------------- Begin code without a home ---------------------
119
120 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
121 require "util.datamanager".set_data_path(data_path);
122 require "util.datamanager".set_callback(function(username, host, datastore)
123         return config.get(host, "core", "anonymous_login");
124 end);
125
126 ----------- End of out-of-place code --------------
127
128 -- Function to reload the config file
129 function prosody_reload_config()
130         log("info", "Reloading configuration file");
131         eventmanager.fire_event("reloading-config");
132         local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
133         if not ok then
134                 if level == "parser" then
135                         log("error", "There was an error parsing the configuration file: %s", tostring(err));
136                 elseif level == "file" then
137                         log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
138                 end
139         end
140 end
141
142 -- Function to reopen logfiles
143 function prosody.reopen_logfiles()
144         log("info", "Re-opening log files");
145         eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
146 end
147
148 -- Temporary
149 prosody_reopen_logfiles = prosody.reopen_logfiles;
150
151 -- Function to initiate prosody shutdown
152 function prosody.shutdown(reason)
153         log("info", "Shutting down: %s", reason or "unknown reason");
154         eventmanager.fire_event("server-stopping", { reason = reason });
155         server.setquitting(true);
156 end
157
158 -- Temporary
159 prosody_shutdown = prosody.shutdown;
160
161 -- Signal to modules that we are ready to start
162 eventmanager.fire_event("server-starting");
163
164 -- Load SSL settings from config, and create a ctx table
165 local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
166 if global_ssl_ctx then
167         local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
168         setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
169 end
170
171 -- start listening on sockets
172 function net_activate_ports(option, listener, default, conntype)
173         local ports = config.get("*", "core", option.."_ports") or default;
174         if type(ports) == "number" then ports = {ports} end;
175         
176         if type(ports) ~= "table" then
177                 log("error", "core."..option.." is not a table");
178         else
179                 for _, port in ipairs(ports) do
180                         if type(port) ~= "number" then
181                                 log("error", "Non-numeric "..option.."_ports: "..tostring(port));
182                         else
183                                 cl.start(listener, { 
184                                         ssl = conntype ~= "tcp" and global_ssl_ctx,
185                                         port = port,
186                                         interface = config.get("*", "core", option.."_interface"),
187                                         type = conntype
188                                 });
189                         end
190                 end
191         end
192 end
193
194 net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
195 net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
196 net_activate_ports("component", "xmppcomponent", {}, "tcp");
197 net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
198
199 if cl.get("console") then
200         cl.start("console", { interface = config.get("*", "core", "console_interface") or "127.0.0.1" })
201 end
202
203 -- Catch global accesses --
204 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 }
205
206 function prosody.unlock_globals()
207         setmetatable(_G, nil);
208 end
209
210 function prosody.lock_globals()
211         setmetatable(_G, locked_globals_mt);
212 end
213
214 -- And lock now...
215 lock_globals();
216
217 eventmanager.fire_event("server-started");
218
219 -- Error handler for errors that make it this far
220 local function catch_uncaught_error(err)
221         if err:match("%d*: interrupted!$") then
222                 return "quitting";
223         end
224         
225         log("error", "Top-level error, please report:\n%s", tostring(err));
226         local traceback = debug.traceback("", 2);
227         if traceback then
228                 log("error", "%s", traceback);
229         end
230         
231         eventmanager.fire_event("very-bad-error", "*", err, traceback);
232 end
233
234 while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
235         socket.sleep(0.2);
236 end
237
238 log("info", "Shutdown status: Cleaning up");
239 eventmanager.fire_event("server-cleanup");
240
241 -- Ok, we're quitting I know, but we
242 -- need to do some tidying before we go :)
243 server.setquitting(false);
244
245 log("info", "Shutdown status: Closing all active sessions");
246 for hostname, host in pairs(hosts) do
247         log("debug", "Shutdown status: Closing client connections for %s", hostname)
248         if host.sessions then
249                 for username, user in pairs(host.sessions) do
250                         for resource, session in pairs(user.sessions) do
251                                 log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
252                                 session:close("system-shutdown");
253                         end
254                 end
255         end
256         
257         log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
258         if host.s2sout then
259                 for remotehost, session in pairs(host.s2sout) do
260                         if session.close then
261                                 session:close("system-shutdown");
262                         else
263                                 log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
264                         end
265                 end
266         end
267 end
268
269 log("info", "Shutdown status: Closing all server connections");
270 server.closeall();
271
272 server.setquitting(true);
273
274 eventmanager.fire_event("server-stopped");
275 log("info", "Shutdown status: Complete!");