cd8816b19f0ba46b23d6acdef78731e56a27653e
[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
72 hosts = {};
73
74 --- Load and initialise core modules
75 require "util.import"
76 require "core.xmlhandlers"
77 require "core.rostermanager"
78 require "core.eventmanager"
79 require "core.hostmanager"
80 require "core.modulemanager"
81 require "core.usermanager"
82 require "core.sessionmanager"
83 require "core.stanza_router"
84
85 require "util.array"
86 require "util.iterators"
87 require "util.timer"
88
89 -- Commented to protect us from 
90 -- the second kind of people
91 --[[ 
92 pcall(require, "remdebug.engine");
93 if remdebug then remdebug.engine.start() end
94 ]]
95
96 local cl = require "net.connlisteners";
97
98 require "util.stanza"
99 require "util.jid"
100
101 ------------------------------------------------------------------------
102
103
104 ------------- Begin code without a home ---------------------
105
106 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
107 require "util.datamanager".set_data_path(data_path);
108 require "util.datamanager".set_callback(function(username, host, datastore)
109         return config.get(host, "core", "anonymous_login");
110 end);
111
112 ----------- End of out-of-place code --------------
113
114 -- Function to reload the config file
115 function prosody_reload_config()
116         log("info", "Reloading configuration file");
117         eventmanager.fire_event("reloading-config");
118         local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
119         if not ok then
120                 if level == "parser" then
121                         log("error", "There was an error parsing the configuration file: %s", tostring(err));
122                 elseif level == "file" then
123                         log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
124                 end
125         end
126 end
127
128 -- Function to reopen logfiles
129 function prosody_reopen_logfiles()
130         log("info", "Re-opening log files");
131         eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
132 end
133
134 -- Function to initiate prosody shutdown
135 function prosody_shutdown(reason)
136         log("info", "Shutting down: %s", reason or "unknown reason");
137         eventmanager.fire_event("server-stopping", { reason = reason });
138         server.setquitting(true);
139 end
140
141 -- Signal to modules that we are ready to start
142 eventmanager.fire_event("server-starting");
143
144 -- Load SSL settings from config, and create a ctx table
145 local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
146 if global_ssl_ctx then
147         local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
148         setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
149 end
150
151 -- start listening on sockets
152 function net_activate_ports(option, listener, default, conntype)
153         local ports = config.get("*", "core", option.."_ports") or default;
154         if type(ports) == "number" then ports = {ports} end;
155         
156         if type(ports) ~= "table" then
157                 log("error", "core."..option.." is not a table");
158         else
159                 for _, port in ipairs(ports) do
160                         if type(port) ~= "number" then
161                                 log("error", "Non-numeric "..option.."_ports: "..tostring(port));
162                         else
163                                 cl.start(listener, { 
164                                         ssl = conntype ~= "tcp" and global_ssl_ctx,
165                                         port = port,
166                                         interface = config.get("*", "core", option.."_interface"),
167                                         type = conntype
168                                 });
169                         end
170                 end
171         end
172 end
173
174 net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
175 net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
176 net_activate_ports("component", "xmppcomponent", {}, "tcp");
177 net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
178
179 if cl.get("console") then
180         cl.start("console", { interface = config.get("*", "core", "console_interface") or "127.0.0.1" })
181 end
182
183 -- Catch global accesses --
184 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 }
185
186 function unlock_globals()
187         setmetatable(_G, nil);
188 end
189
190 function lock_globals()
191         setmetatable(_G, locked_globals_mt);
192 end
193
194 -- And lock now...
195 lock_globals();
196
197 eventmanager.fire_event("server-started");
198
199 -- Error handler for errors that make it this far
200 local function catch_uncaught_error(err)
201         if err:match("%d*: interrupted!$") then
202                 return "quitting";
203         end
204         
205         log("error", "Top-level error, please report:\n%s", tostring(err));
206         local traceback = debug.traceback("", 2);
207         if traceback then
208                 log("error", "%s", traceback);
209         end
210         
211         eventmanager.fire_event("very-bad-error", "*", err, traceback);
212 end
213
214 while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
215         socket.sleep(0.2);
216 end
217
218 log("info", "Shutdown status: Cleaning up");
219 eventmanager.fire_event("server-cleanup");
220
221 -- Ok, we're quitting I know, but we
222 -- need to do some tidying before we go :)
223 server.setquitting(false);
224
225 log("info", "Shutdown status: Closing all active sessions");
226 for hostname, host in pairs(hosts) do
227         log("debug", "Shutdown status: Closing client connections for %s", hostname)
228         if host.sessions then
229                 for username, user in pairs(host.sessions) do
230                         for resource, session in pairs(user.sessions) do
231                                 log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
232                                 session:close("system-shutdown");
233                         end
234                 end
235         end
236         
237         log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
238         if host.s2sout then
239                 for remotehost, session in pairs(host.s2sout) do
240                         if session.close then
241                                 session:close("system-shutdown");
242                         else
243                                 log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
244                         end
245                 end
246         end
247 end
248
249 log("info", "Shutdown status: Closing all server connections");
250 server.closeall();
251
252 server.setquitting(true);
253
254 eventmanager.fire_event("server-stopped");
255 log("info", "Shutdown status: Complete!");