IMPORTANT: due to a drive failure, as of 13-Mar-2021, the Mercurial repository had to be re-mirrored, which changed every commit SHA. The old SHAs and trees are backed up in the vault branches. Please migrate to the new branches as soon as you can.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
prosody/prosody

468 lines
14 KiB

#!/usr/bin/env lua
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- Will be modified by configure script if run --
CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
CFG_DATADIR=os.getenv("PROSODY_DATADIR");
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- Tell Lua where to find our libraries
if CFG_SOURCEDIR then
package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
end
package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
-- Substitute ~ with path to home directory in data path
if CFG_DATADIR then
if os.getenv("HOME") then
CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
end
end
-- Load the config-parsing module
config = require "core.configmanager"
-- -- -- --
-- Define the functions we call during startup, the
-- actual startup happens right at the end, where these
-- functions get called
function read_config()
local filenames = {};
local filename;
if arg[1] == "--config" and arg[2] then
table.insert(filenames, arg[2]);
if CFG_CONFIGDIR then
table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
end
else
for _, format in ipairs(config.parsers()) do
table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
end
end
for _,_filename in ipairs(filenames) do
filename = _filename;
local file = io.open(filename);
if file then
file:close();
CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
break;
end
end
local ok, level, err = config.load(filename);
if not ok then
print("\n");
print("**************************");
if level == "parser" then
print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
print("");
elseif level == "file" then
print("Prosody was unable to find the configuration file.");
print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
end
print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
print("Good luck!");
print("**************************");
print("");
os.exit(1);
end
end
function load_libraries()
-- Load socket framework
server = require "net.server"
end
function init_logging()
-- Initialize logging
require "core.loggingmanager"
end
function check_dependencies()
-- Check runtime dependencies
if not require "util.dependencies".check_dependencies() then
os.exit(1);
end
end
function sandbox_require()
-- Replace require() with one that doesn't pollute _G, required
-- for neat sandboxing of modules
local _realG = _G;
local _real_require = require;
function require(...)
local curr_env = getfenv(2);
local curr_env_mt = getmetatable(getfenv(2));
local _realG_mt = getmetatable(_realG);
if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
local old_newindex
old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
local ret = _real_require(...);
_realG_mt.__newindex = old_newindex;
return ret;
end
return _real_require(...);
end
end
function set_function_metatable()
local mt = {};
function mt.__index(f, upvalue)
local i, name, value = 0;
repeat
i = i + 1;
name, value = debug.getupvalue(f, i);
until name == upvalue or name == nil;
return value;
end
function mt.__newindex(f, upvalue, value)
local i, name = 0;
repeat
i = i + 1;
name = debug.getupvalue(f, i);
until name == upvalue or name == nil;
if name then
debug.setupvalue(f, i, value);
end
end
function mt.__tostring(f)
local info = debug.getinfo(f);
return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
end
debug.setmetatable(function() end, mt);
end
function init_global_state()
bare_sessions = {};
full_sessions = {};
hosts = {};
-- Global 'prosody' object
prosody = {};
local prosody = prosody;
prosody.bare_sessions = bare_sessions;
prosody.full_sessions = full_sessions;
prosody.hosts = hosts;
prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR,
plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
prosody.arg = _G.arg;
prosody.events = require "util.events".new();
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
elseif package.config:sub(1,1) == "/" then
prosody.platform = "posix";
end
prosody.installed = nil;
if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
prosody.installed = true;
end
-- Function to reload the config file
function prosody.reload_config()
log("info", "Reloading configuration file");
prosody.events.fire_event("reloading-config");
local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
if not ok then
if level == "parser" then
log("error", "There was an error parsing the configuration file: %s", tostring(err));
elseif level == "file" then
log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
end
end
return ok, (err and tostring(level)..": "..tostring(err)) or nil;
end
-- Function to reopen logfiles
function prosody.reopen_logfiles()
log("info", "Re-opening log files");
eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
prosody.events.fire_event("reopen-log-files");
end
-- Function to initiate prosody shutdown
function prosody.shutdown(reason)
log("info", "Shutting down: %s", reason or "unknown reason");
prosody.shutdown_reason = reason;
prosody.events.fire_event("server-stopping", {reason = reason});
server.setquitting(true);
end
-- Load SSL settings from config, and create a ctx table
local certmanager = require "core.certmanager";
local global_ssl_ctx = certmanager.create_context("*", "server");
prosody.global_ssl_ctx = global_ssl_ctx;
local cl = require "net.connlisteners";
function prosody.net_activate_ports(option, listener, default, conntype)
conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
local ports_option = option and option.."_ports" or "ports";
if not cl.get(listener) then return; end
local ports = config.get("*", "core", ports_option) or default;
if type(ports) == "number" then ports = {ports} end;
if type(ports) ~= "table" then
log("error", "core."..ports_option.." is not a table");
else
for _, port in ipairs(ports) do
port = tonumber(port);
if type(port) ~= "number" then
log("error", "Non-numeric "..ports_option..": "..tostring(port));
else
local ok, err = cl.start(listener, {
ssl = conntype == "ssl" and global_ssl_ctx,
port = port,
interface = (option and config.get("*", "core", option.."_interface"))
or cl.get(listener).default_interface
or config.get("*", "core", "interface"),
type = conntype
});
if not ok then
local friendly_message = err;
if err:match(" in use") then
if port == 5222 or port == 5223 or port == 5269 then
friendly_message = "check that Prosody or another XMPP server is "
.."not already running and using this port";
elseif port == 80 or port == 81 then
friendly_message = "check that a HTTP server is not already using "
.."this port";
elseif port == 5280 then
friendly_message = "check that Prosody or a BOSH connection manager "
.."is not already running";
else
friendly_message = "this port is in use by another application";
end
elseif err:match("permission") then
friendly_message = "Prosody does not have sufficient privileges to use this port";
elseif err == "no ssl context" then
if not config.get("*", "core", "ssl") then
friendly_message = "there is no 'ssl' config under Host \"*\" which is "
.."require for legacy SSL ports";
else
friendly_message = "initializing SSL support failed, see previous log entries";
end
end
log("error", "Failed to open server port %d, %s", port, friendly_message);
end
end
end
end
end
end
function read_version()
-- Try to determine version
local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
if version_file then
prosody.version = version_file:read("*a"):gsub("%s*$", "");
version_file:close();
if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
prosody.version = "hg:"..prosody.version;
end
else
prosody.version = "unknown";
end
end
function load_secondary_libraries()
--- Load and initialise core modules
require "util.import"
require "core.xmlhandlers"
require "core.rostermanager"
require "core.eventmanager"
require "core.hostmanager"
require "core.modulemanager"
require "core.usermanager"
require "core.sessionmanager"
require "core.stanza_router"
require "net.http"
require "util.array"
require "util.datetime"
require "util.iterators"
require "util.timer"
require "util.helpers"
pcall(require, "util.signal") -- Not on Windows
-- Commented to protect us from
-- the second kind of people
--[[
pcall(require, "remdebug.engine");
if remdebug then remdebug.engine.start() end
]]
require "net.connlisteners";
require "util.stanza"
require "util.jid"
end
function init_data_store()
local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
require "util.datamanager".set_data_path(data_path);
require "util.datamanager".add_callback(function(username, host, datastore, data)
if config.get(host, "core", "anonymous_login") then
return false;
end
return username, host, datastore, data;
end);
end
function prepare_to_start()
log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
-- Signal to modules that we are ready to start
eventmanager.fire_event("server-starting");
prosody.events.fire_event("server-starting");
-- start listening on sockets
if config.get("*", "core", "ports") then
prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
if config.get("*", "core", "ssl_ports") then
prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
end
else
prosody.net_activate_ports("c2s", "xmppclient", {5222});
prosody.net_activate_ports("s2s", "xmppserver", {5269});
prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
end
prosody.start_time = os.time();
end
function init_global_protection()
-- Catch global accesses
local locked_globals_mt = {
__index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
__newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
};
function prosody.unlock_globals()
setmetatable(_G, nil);
end
function prosody.lock_globals()
setmetatable(_G, locked_globals_mt);
end
-- And lock now...
prosody.lock_globals();
end
function loop()
-- Error handler for errors that make it this far
local function catch_uncaught_error(err)
if type(err) == "string" and err:match("interrupted!$") then
return "quitting";
end
log("error", "Top-level error, please report:\n%s", tostring(err));
local traceback = debug.traceback("", 2);
if traceback then
log("error", "%s", traceback);
end
prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
end
while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
socket.sleep(0.2);
end
end
function cleanup()
log("info", "Shutdown status: Cleaning up");
prosody.events.fire_event("server-cleanup");
-- Ok, we're quitting I know, but we
-- need to do some tidying before we go :)
server.setquitting(false);
log("info", "Shutdown status: Closing all active sessions");
for hostname, host in pairs(hosts) do
log("debug", "Shutdown status: Closing client connections for %s", hostname)
if host.sessions then
local reason = { condition = "system-shutdown", text = "Server is shutting down" };
if prosody.shutdown_reason then
reason.text = reason.text..": "..prosody.shutdown_reason;
end
for username, user in pairs(host.sessions) do
for resource, session in pairs(user.sessions) do
log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
session:close(reason);
end
end
end
log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
if host.s2sout then
for remotehost, session in pairs(host.s2sout) do
if session.close then
session:close("system-shutdown");
else
log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
end
end
end
end
log("info", "Shutdown status: Closing all server connections");
server.closeall();
server.setquitting(true);
end
-- Are you ready? :)
-- These actions are in a strict order, as many depend on
-- previous steps to have already been performed
read_config();
init_logging();
check_dependencies();
sandbox_require();
set_function_metatable();
load_libraries();
init_global_state();
read_version();
log("info", "Hello and welcome to Prosody version %s", prosody.version);
load_secondary_libraries();
init_data_store();
init_global_protection();
prepare_to_start();
eventmanager.fire_event("server-started");
prosody.events.fire_event("server-started");
loop();
log("info", "Shutting down...");
cleanup();
eventmanager.fire_event("server-stopped");
prosody.events.fire_event("server-stopped");
log("info", "Shutdown complete");