net.httpserver: Allow response.body to be a non-string
[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=os.getenv("PROSODY_SRCDIR");
13 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
14 CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
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         if not cl.get(listener) then return; end
192         local ports = config.get("*", "core", option.."_ports") or default;
193         if type(ports) == "number" then ports = {ports} end;
194         
195         if type(ports) ~= "table" then
196                 log("error", "core."..option.." is not a table");
197         else
198                 for _, port in ipairs(ports) do
199                         if type(port) ~= "number" then
200                                 log("error", "Non-numeric "..option.."_ports: "..tostring(port));
201                         else
202                                 cl.start(listener, { 
203                                         ssl = conntype ~= "tcp" and global_ssl_ctx,
204                                         port = port,
205                                         interface = config.get("*", "core", option.."_interface") 
206                                                 or cl.get(listener).default_interface 
207                                                 or config.get("*", "core", "interface"),
208                                         type = conntype
209                                 });
210                         end
211                 end
212         end
213 end
214
215 net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
216 net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
217 net_activate_ports("component", "xmppcomponent", {}, "tcp");
218 net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
219 net_activate_ports("console", "console", {5582}, "tcp");
220
221 -- Catch global accesses --
222 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 }
223
224 function prosody.unlock_globals()
225         setmetatable(_G, nil);
226 end
227
228 function prosody.lock_globals()
229         setmetatable(_G, locked_globals_mt);
230 end
231
232 -- And lock now...
233 prosody.lock_globals();
234
235 prosody.start_time = os.time();
236
237 eventmanager.fire_event("server-started");
238 prosody.events.fire_event("server-started");
239
240 -- Error handler for errors that make it this far
241 local function catch_uncaught_error(err)
242         if err:match("%d*: interrupted!$") then
243                 return "quitting";
244         end
245         
246         log("error", "Top-level error, please report:\n%s", tostring(err));
247         local traceback = debug.traceback("", 2);
248         if traceback then
249                 log("error", "%s", traceback);
250         end
251         
252         prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
253 end
254
255 while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
256         socket.sleep(0.2);
257 end
258
259 log("info", "Shutdown status: Cleaning up");
260 prosody.events.fire_event("server-cleanup");
261
262 -- Ok, we're quitting I know, but we
263 -- need to do some tidying before we go :)
264 server.setquitting(false);
265
266 log("info", "Shutdown status: Closing all active sessions");
267 for hostname, host in pairs(hosts) do
268         log("debug", "Shutdown status: Closing client connections for %s", hostname)
269         if host.sessions then
270                 for username, user in pairs(host.sessions) do
271                         for resource, session in pairs(user.sessions) do
272                                 log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
273                                 session:close("system-shutdown");
274                         end
275                 end
276         end
277         
278         log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
279         if host.s2sout then
280                 for remotehost, session in pairs(host.s2sout) do
281                         if session.close then
282                                 session:close("system-shutdown");
283                         else
284                                 log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
285                         end
286                 end
287         end
288 end
289
290 log("info", "Shutdown status: Closing all server connections");
291 server.closeall();
292
293 server.setquitting(true);
294
295 eventmanager.fire_event("server-stopped");
296 prosody.events.fire_event("server-stopped");
297 log("info", "Shutdown status: Complete!");