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