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