util.encodings: Spell out all IDNA 2008 options ICU has
[prosody.git] / util / startup.lua
blob580410c94e11335705f6ee3a788a04fffad5d0d5
1 -- Ignore the CFG_* variables
2 -- luacheck: ignore 113/CFG_CONFIGDIR 113/CFG_SOURCEDIR 113/CFG_DATADIR 113/CFG_PLUGINDIR
3 local startup = {};
5 local prosody = { events = require "util.events".new() };
6 local logger = require "util.logger";
7 local log = logger.init("startup");
9 local config = require "core.configmanager";
10 local config_warnings;
12 local dependencies = require "util.dependencies";
14 local original_logging_config;
16 function startup.read_config()
17 local filenames = {};
19 local filename;
20 if arg[1] == "--config" and arg[2] then
21 table.insert(filenames, arg[2]);
22 if CFG_CONFIGDIR then
23 table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
24 end
25 table.remove(arg, 1); table.remove(arg, 1);
26 elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl
27 table.insert(filenames, os.getenv("PROSODY_CONFIG"));
28 else
29 table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
30 end
31 for _,_filename in ipairs(filenames) do
32 filename = _filename;
33 local file = io.open(filename);
34 if file then
35 file:close();
36 prosody.config_file = filename;
37 CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$"); -- luacheck: ignore 111
38 break;
39 end
40 end
41 prosody.config_file = filename
42 local ok, level, err = config.load(filename);
43 if not ok then
44 print("\n");
45 print("**************************");
46 if level == "parser" then
47 print("A problem occurred while reading the config file "..filename);
48 print("");
49 local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
50 if err:match("chunk has too many syntax levels$") then
51 print("An Include statement in a config file is including an already-included");
52 print("file and causing an infinite loop. An Include statement in a config file is...");
53 else
54 print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
55 end
56 print("");
57 elseif level == "file" then
58 print("Prosody was unable to find the configuration file.");
59 print("We looked for: "..filename);
60 print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
61 print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
62 end
63 print("More help on configuring Prosody can be found at https://prosody.im/doc/configure");
64 print("Good luck!");
65 print("**************************");
66 print("");
67 os.exit(1);
68 elseif err and #err > 0 then
69 config_warnings = err;
70 end
71 prosody.config_loaded = true;
72 end
74 function startup.check_dependencies()
75 if not dependencies.check_dependencies() then
76 os.exit(1);
77 end
78 end
80 -- luacheck: globals socket server
82 function startup.load_libraries()
83 -- Load socket framework
84 -- luacheck: ignore 111/server 111/socket
85 socket = require "socket";
86 server = require "net.server"
87 end
89 function startup.init_logging()
90 -- Initialize logging
91 local loggingmanager = require "core.loggingmanager"
92 loggingmanager.reload_logging();
93 prosody.events.add_handler("config-reloaded", function ()
94 prosody.events.fire_event("reopen-log-files");
95 end);
96 prosody.events.add_handler("reopen-log-files", function ()
97 loggingmanager.reload_logging();
98 prosody.events.fire_event("logging-reloaded");
99 end);
102 function startup.log_startup_warnings()
103 dependencies.log_warnings();
104 if config_warnings then
105 for _, warning in ipairs(config_warnings) do
106 log("warn", "Configuration warning: %s", warning);
111 function startup.sanity_check()
112 for host, host_config in pairs(config.getconfig()) do
113 if host ~= "*"
114 and host_config.enabled ~= false
115 and not host_config.component_module then
116 return;
119 log("error", "No enabled VirtualHost entries found in the config file.");
120 log("error", "At least one active host is required for Prosody to function. Exiting...");
121 os.exit(1);
124 function startup.sandbox_require()
125 -- Replace require() with one that doesn't pollute _G, required
126 -- for neat sandboxing of modules
127 -- luacheck: ignore 113/getfenv 111/require
128 local _realG = _G;
129 local _real_require = require;
130 local getfenv = getfenv or function (f)
131 -- FIXME: This is a hack to replace getfenv() in Lua 5.2
132 local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1);
133 if name == "_ENV" then
134 return env;
137 function require(...) -- luacheck: ignore 121
138 local curr_env = getfenv(2);
139 local curr_env_mt = getmetatable(curr_env);
140 local _realG_mt = getmetatable(_realG);
141 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
142 local old_newindex, old_index;
143 old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
144 old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) -- luacheck: ignore 212/_G
145 return rawget(curr_env, k);
146 end;
147 local ret = _real_require(...);
148 _realG_mt.__newindex = old_newindex;
149 _realG_mt.__index = old_index;
150 return ret;
152 return _real_require(...);
156 function startup.set_function_metatable()
157 local mt = {};
158 function mt.__index(f, upvalue)
159 local i, name, value = 0;
160 repeat
161 i = i + 1;
162 name, value = debug.getupvalue(f, i);
163 until name == upvalue or name == nil;
164 return value;
166 function mt.__newindex(f, upvalue, value)
167 local i, name = 0;
168 repeat
169 i = i + 1;
170 name = debug.getupvalue(f, i);
171 until name == upvalue or name == nil;
172 if name then
173 debug.setupvalue(f, i, value);
176 function mt.__tostring(f)
177 local info = debug.getinfo(f);
178 return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
180 debug.setmetatable(function() end, mt);
183 function startup.detect_platform()
184 prosody.platform = "unknown";
185 if os.getenv("WINDIR") then
186 prosody.platform = "windows";
187 elseif package.config:sub(1,1) == "/" then
188 prosody.platform = "posix";
192 function startup.detect_installed()
193 prosody.installed = nil;
194 if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
195 prosody.installed = true;
199 function startup.init_global_state()
200 -- luacheck: ignore 121
201 prosody.bare_sessions = {};
202 prosody.full_sessions = {};
203 prosody.hosts = {};
205 -- COMPAT: These globals are deprecated
206 -- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts
207 bare_sessions = prosody.bare_sessions;
208 full_sessions = prosody.full_sessions;
209 hosts = prosody.hosts;
211 prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".",
212 plugins = CFG_PLUGINDIR or "plugins", data = "data" };
214 prosody.arg = _G.arg;
216 _G.log = logger.init("general");
217 prosody.log = logger.init("general");
219 startup.detect_platform();
220 startup.detect_installed();
221 _G.prosody = prosody;
224 function startup.setup_datadir()
225 prosody.paths.data = config.get("*", "data_path") or CFG_DATADIR or "data";
228 function startup.setup_plugindir()
229 local custom_plugin_paths = config.get("*", "plugin_paths");
230 local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins";
231 local path_sep = package.config:sub(3,3);
232 installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path);
233 require "lfs".mkdir(installer_plugin_path);
234 require"util.paths".complement_lua_path(installer_plugin_path);
235 if custom_plugin_paths then
236 -- path1;path2;path3;defaultpath...
237 -- luacheck: ignore 111
238 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
239 prosody.paths.plugins = CFG_PLUGINDIR;
241 CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins");
242 prosody.paths.plugins = CFG_PLUGINDIR;
245 function startup.chdir()
246 if prosody.installed then
247 -- Change working directory to data path.
248 require "lfs".chdir(prosody.paths.data);
252 function startup.add_global_prosody_functions()
253 -- Function to reload the config file
254 function prosody.reload_config()
255 log("info", "Reloading configuration file");
256 prosody.events.fire_event("reloading-config");
257 local ok, level, err = config.load(prosody.config_file);
258 if not ok then
259 if level == "parser" then
260 log("error", "There was an error parsing the configuration file: %s", err);
261 elseif level == "file" then
262 log("error", "Couldn't read the config file when trying to reload: %s", err);
264 else
265 prosody.events.fire_event("config-reloaded", {
266 filename = prosody.config_file,
267 config = config.getconfig(),
270 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
273 -- Function to reopen logfiles
274 function prosody.reopen_logfiles()
275 log("info", "Re-opening log files");
276 prosody.events.fire_event("reopen-log-files");
279 -- Function to initiate prosody shutdown
280 function prosody.shutdown(reason, code)
281 log("info", "Shutting down: %s", reason or "unknown reason");
282 prosody.shutdown_reason = reason;
283 prosody.shutdown_code = code;
284 prosody.events.fire_event("server-stopping", {
285 reason = reason;
286 code = code;
288 server.setquitting(true);
292 function startup.load_secondary_libraries()
293 --- Load and initialise core modules
294 require "util.import"
295 require "util.xmppstream"
296 require "core.stanza_router"
297 require "core.statsmanager"
298 require "core.hostmanager"
299 require "core.portmanager"
300 require "core.modulemanager"
301 require "core.usermanager"
302 require "core.rostermanager"
303 require "core.sessionmanager"
304 package.loaded['core.componentmanager'] = setmetatable({},{__index=function()
305 -- COMPAT which version?
306 log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)"));
307 return function() end
308 end});
310 require "util.array"
311 require "util.datetime"
312 require "util.iterators"
313 require "util.timer"
314 require "util.helpers"
316 pcall(require, "util.signal") -- Not on Windows
318 -- Commented to protect us from
319 -- the second kind of people
320 --[[
321 pcall(require, "remdebug.engine");
322 if remdebug then remdebug.engine.start() end
325 require "util.stanza"
326 require "util.jid"
329 function startup.init_http_client()
330 local http = require "net.http"
331 local config_ssl = config.get("*", "ssl") or {}
332 local https_client = config.get("*", "client_https_ssl")
333 http.default.options.sslctx = require "core.certmanager".create_context("client_https port 0", "client",
334 { capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client);
337 function startup.init_data_store()
338 require "core.storagemanager";
341 function startup.prepare_to_start()
342 log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
343 -- Signal to modules that we are ready to start
344 prosody.events.fire_event("server-starting");
345 prosody.start_time = os.time();
348 function startup.init_global_protection()
349 -- Catch global accesses
350 -- luacheck: ignore 212/t
351 local locked_globals_mt = {
352 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
353 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
356 function prosody.unlock_globals()
357 setmetatable(_G, nil);
360 function prosody.lock_globals()
361 setmetatable(_G, locked_globals_mt);
364 -- And lock now...
365 prosody.lock_globals();
368 function startup.read_version()
369 -- Try to determine version
370 local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
371 prosody.version = "unknown";
372 if version_file then
373 prosody.version = version_file:read("*a"):gsub("%s*$", "");
374 version_file:close();
375 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
376 prosody.version = "hg:"..prosody.version;
378 else
379 local hg = require"util.mercurial";
380 local hgid = hg.check_id(CFG_SOURCEDIR or ".");
381 if hgid then prosody.version = "hg:" .. hgid; end
385 function startup.log_greeting()
386 log("info", "Hello and welcome to Prosody version %s", prosody.version);
389 function startup.notify_started()
390 prosody.events.fire_event("server-started");
393 -- Override logging config (used by prosodyctl)
394 function startup.force_console_logging()
395 original_logging_config = config.get("*", "log");
396 config.set("*", "log", { { levels = { min = os.getenv("PROSODYCTL_LOG_LEVEL") or "info" }, to = "console" } });
399 function startup.switch_user()
400 -- Switch away from root and into the prosody user --
401 -- NOTE: This function is only used by prosodyctl.
402 -- The prosody process is built with the assumption that
403 -- it is already started as the appropriate user.
405 local want_pposix_version = "0.4.0";
406 local have_pposix, pposix = pcall(require, "util.pposix");
408 if have_pposix and pposix then
409 if pposix._VERSION ~= want_pposix_version then
410 print(string.format("Unknown version (%s) of binary pposix module, expected %s",
411 tostring(pposix._VERSION), want_pposix_version));
412 os.exit(1);
414 prosody.current_uid = pposix.getuid();
415 local arg_root = arg[1] == "--root";
416 if arg_root then table.remove(arg, 1); end
417 if prosody.current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then
418 -- We haz root!
419 local desired_user = config.get("*", "prosody_user") or "prosody";
420 local desired_group = config.get("*", "prosody_group") or desired_user;
421 local ok, err = pposix.setgid(desired_group);
422 if ok then
423 ok, err = pposix.initgroups(desired_user);
425 if ok then
426 ok, err = pposix.setuid(desired_user);
427 if ok then
428 -- Yay!
429 prosody.switched_user = true;
432 if not prosody.switched_user then
433 -- Boo!
434 print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err));
435 else
436 -- Make sure the Prosody user can read the config
437 local conf, err, errno = io.open(prosody.config_file);
438 if conf then
439 conf:close();
440 else
441 print("The config file is not readable by the '"..desired_user.."' user.");
442 print("Prosody will not be able to read it.");
443 print("Error was "..err);
444 os.exit(1);
449 -- Set our umask to protect data files
450 pposix.umask(config.get("*", "umask") or "027");
451 pposix.setenv("HOME", prosody.paths.data);
452 pposix.setenv("PROSODY_CONFIG", prosody.config_file);
453 else
454 print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
455 print("For more help send the below error to us through https://prosody.im/discuss");
456 print(tostring(pposix))
457 os.exit(1);
461 function startup.check_unwriteable()
462 local function test_writeable(filename)
463 local f, err = io.open(filename, "a");
464 if not f then
465 return false, err;
467 f:close();
468 return true;
471 local unwriteable_files = {};
472 if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
473 local ok, err = test_writeable(original_logging_config);
474 if not ok then
475 table.insert(unwriteable_files, err);
477 elseif type(original_logging_config) == "table" then
478 for _, rule in ipairs(original_logging_config) do
479 if rule.filename then
480 local ok, err = test_writeable(rule.filename);
481 if not ok then
482 table.insert(unwriteable_files, err);
488 if #unwriteable_files > 0 then
489 print("One of more of the Prosody log files are not");
490 print("writeable, please correct the errors and try");
491 print("starting prosodyctl again.");
492 print("");
493 for _, err in ipairs(unwriteable_files) do
494 print(err);
496 print("");
497 os.exit(1);
501 function startup.make_host(hostname)
502 return {
503 type = "local",
504 events = prosody.events,
505 modules = {},
506 sessions = {},
507 users = require "core.usermanager".new_null_provider(hostname)
511 function startup.make_dummy_hosts()
512 -- When running under prosodyctl, we don't want to
513 -- fully initialize the server, so we populate prosody.hosts
514 -- with just enough things for most code to work correctly
515 -- luacheck: ignore 122/hosts
516 prosody.core_post_stanza = function () end; -- TODO: mod_router!
518 for hostname in pairs(config.getconfig()) do
519 prosody.hosts[hostname] = startup.make_host(hostname);
523 -- prosodyctl only
524 function startup.prosodyctl()
525 startup.init_global_state();
526 startup.read_config();
527 startup.force_console_logging();
528 startup.init_logging();
529 startup.setup_plugindir();
530 startup.setup_datadir();
531 startup.chdir();
532 startup.read_version();
533 startup.switch_user();
534 startup.check_dependencies();
535 startup.log_startup_warnings();
536 startup.check_unwriteable();
537 startup.load_libraries();
538 startup.init_http_client();
539 startup.make_dummy_hosts();
542 function startup.prosody()
543 -- These actions are in a strict order, as many depend on
544 -- previous steps to have already been performed
545 startup.init_global_state();
546 startup.read_config();
547 startup.init_logging();
548 startup.sanity_check();
549 startup.sandbox_require();
550 startup.set_function_metatable();
551 startup.check_dependencies();
552 startup.init_logging();
553 startup.load_libraries();
554 startup.setup_plugindir();
555 startup.setup_datadir();
556 startup.chdir();
557 startup.add_global_prosody_functions();
558 startup.read_version();
559 startup.log_greeting();
560 startup.log_startup_warnings();
561 startup.load_secondary_libraries();
562 startup.init_http_client();
563 startup.init_data_store();
564 startup.init_global_protection();
565 startup.prepare_to_start();
566 startup.notify_started();
569 return startup;