aboutsummaryrefslogtreecommitdiffstats
path: root/core/componentmanager.lua
blob: 8e4b9c2a997ddec6bdff8f8898ce81cf2a38604b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
-- 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.
--

local prosody = _G.prosody;
local log = require "util.logger".init("componentmanager");
local certmanager = require "core.certmanager";
local configmanager = require "core.configmanager";
local modulemanager = require "core.modulemanager";
local jid_split = require "util.jid".split;
local fire_event = prosody.events.fire_event;
local events_new = require "util.events".new;
local st = require "util.stanza";
local prosody, hosts = prosody, prosody.hosts;
local ssl = ssl;
local uuid_gen = require "util.uuid".generate;

local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;

local components = {};

local disco_items = require "util.multitable".new();
local NULL = {};

module "componentmanager"

local function default_component_handler(origin, stanza)
	log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
	if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
		origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
	end
end

function load_enabled_components(config)
	local defined_hosts = config or configmanager.getconfig();
		
	for host, host_config in pairs(defined_hosts) do
		if host ~= "*" and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
			hosts[host] = create_component(host);
			hosts[host].connected = false;
			components[host] = default_component_handler;
			local ok, err = modulemanager.load(host, host_config.core.component_module);
			if not ok then
				log("error", "Error loading %s component %s: %s", tostring(host_config.core.component_module), tostring(host), tostring(err));
			else
				fire_event("component-activated", host, host_config);
				log("debug", "Activated %s component: %s", host_config.core.component_module, host);
			end
		end
	end
end

if prosody and prosody.events then
	prosody.events.add_handler("server-starting", load_enabled_components);
end

function handle_stanza(origin, stanza)
	local node, host = jid_split(stanza.attr.to);
	local component = nil;
	if host then
		if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
		if not component then component = components[host]; end
	end
	if component then
		log("debug", "%s stanza being handled by component: %s", stanza.name, host);
		component(origin, stanza, hosts[host]);
	else
		log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
		default_component_handler(origin, stanza);
	end
end

function create_component(host, component, events)
	-- TODO check for host well-formedness
	local ssl_ctx, ssl_ctx_in;
	if host and ssl then
		-- We need to find SSL context to use...
		-- Discussion in prosody@ concluded that
		-- 1 level back is usually enough by default
		local base_host = host:gsub("^[^%.]+%.", "");
		if hosts[base_host] then
			ssl_ctx = hosts[base_host].ssl_ctx;
			ssl_ctx_in = hosts[base_host].ssl_ctx_in;
		else
			-- We have no cert, and no parent host to borrow a cert from
			-- Use global/default cert if there is one
			ssl_ctx = certmanager.create_context(host, "client");
			ssl_ctx_in = certmanager.create_context(host, "server");
		end
	end
	return { type = "component", host = host, connected = true, s2sout = {},
			ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new(),
			dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen(),
			disallow_s2s = configmanager.get(host, "core", "disallow_s2s"); };
end

function register_component(host, component)
	if not hosts[host] or (hosts[host].type == 'component' and not hosts[host].connected) then
		local old_events = hosts[host] and hosts[host].events;

		components[host] = component;
		hosts[host] = create_component(host, component, old_events);

		-- Add events object if not already one
		if not hosts[host].events then
			hosts[host].events = old_events or events_new();
		end

		if not hosts[host].dialback_secret then
			hosts[host].dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
		end

		-- add to disco_items
		if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
			disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
		end
		modulemanager.load(host, "dialback");
		modulemanager.load(host, "tls");
		log("debug", "component added: "..host);
		return hosts[host];
	else
		log("error", "Attempt to set component for existing host: "..host);
	end
end

function deregister_component(host)
	if components[host] then
		modulemanager.unload(host, "tls");
		modulemanager.unload(host, "dialback");
		hosts[host].connected = nil;
		local host_config = configmanager.getconfig()[host];
		if host_config and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
			-- Set default handler
			components[host] = default_component_handler;
		else
			-- Component not in config, or disabled, remove
			hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
			components[host] = nil;
		end
		-- remove from disco_items
		if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
			disco_items:remove(host:sub(host:find(".", 1, true)+1), host);
		end
		log("debug", "component removed: "..host);
		return true;
	else
		log("error", "Attempt to remove component for non-existing host: "..host);
	end
end

function get_children(host)
	return disco_items:get(host) or NULL;
end

return _M;