c05f72292b067a1c4e926e25afeb9aea51d6ccd2
[prosody.git] / core / s2smanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10
11 local hosts = hosts;
12 local core_process_stanza = function(a, b) core_process_stanza(a, b); end
13 local format = string.format;
14 local t_insert, t_sort = table.insert, table.sort;
15 local get_traceback = debug.traceback;
16 local tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable
17     = tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable;
18
19 local initialize_filters = require "util.filters".initialize;
20 local wrapclient = require "net.server".wrapclient;
21 local st = require "stanza";
22 local stanza = st.stanza;
23 local nameprep = require "util.encodings".stringprep.nameprep;
24 local cert_verify_identity = require "util.x509".verify_identity;
25 local new_ip = require "util.ip".new_ip;
26 local rfc3484_dest = require "util.rfc3484".destination;
27
28 local fire_event = prosody.events.fire_event;
29 local uuid_gen = require "util.uuid".generate;
30
31 local logger_init = require "util.logger".init;
32
33 local log = logger_init("s2smanager");
34
35 local sha256_hash = require "util.hashes".sha256;
36
37 local adns, dns = require "net.adns", require "net.dns";
38 local config = require "core.configmanager";
39 local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
40 local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
41 local cfg_sources = config.get("*", "core", "s2s_interface")
42         or config.get("*", "core", "interface");
43 local sources;
44
45 --FIXME: s2sout should create its own resolver w/ timeout
46 dns.settimeout(dns_timeout);
47
48 local prosody = _G.prosody;
49 incoming_s2s = {};
50 prosody.incoming_s2s = incoming_s2s;
51 local incoming_s2s = incoming_s2s;
52
53 module "s2smanager"
54
55 local open_sessions = 0;
56
57 function new_incoming(conn)
58         local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} };
59         if true then
60                 session.trace = newproxy(true);
61                 getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end;
62         end
63         open_sessions = open_sessions + 1;
64         session.log = logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
65         incoming_s2s[session] = true;
66         return session;
67 end
68
69 function new_outgoing(from_host, to_host, connect)
70         local host_session = { to_host = to_host, from_host = from_host, host = from_host,
71                                notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
72         hosts[from_host].s2sout[to_host] = host_session;
73         local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
74         host_session.log = logger_init(conn_name);
75         return host_session;
76 end
77
78 function make_authenticated(session, host)
79         if not session.secure then
80                 local local_host = session.direction == "incoming" and session.to_host or session.from_host;
81                 if config.get(local_host, "core", "s2s_require_encryption") then
82                         session:close({
83                                 condition = "policy-violation",
84                                 text = "Encrypted server-to-server communication is required but was not "
85                                        ..((session.direction == "outgoing" and "offered") or "used")
86                         });
87                 end
88         end
89         if session.type == "s2sout_unauthed" then
90                 session.type = "s2sout";
91         elseif session.type == "s2sin_unauthed" then
92                 session.type = "s2sin";
93                 if host then
94                         if not session.hosts[host] then session.hosts[host] = {}; end
95                         session.hosts[host].authed = true;
96                 end
97         elseif session.type == "s2sin" and host then
98                 if not session.hosts[host] then session.hosts[host] = {}; end
99                 session.hosts[host].authed = true;
100         else
101                 return false;
102         end
103         session.log("debug", "connection %s->%s is now authenticated", session.from_host or "(unknown)", session.to_host or "(unknown)");
104         
105         mark_connected(session);
106         
107         return true;
108 end
109
110 -- Stream is authorised, and ready for normal stanzas
111 function mark_connected(session)
112         local sendq, send = session.sendq, session.sends2s;
113         
114         local from, to = session.from_host, session.to_host;
115         
116         session.log("info", session.direction.." s2s connection "..from.."->"..to.." complete");
117
118         local event_data = { session = session };
119         if session.type == "s2sout" then
120                 prosody.events.fire_event("s2sout-established", event_data);
121                 hosts[session.from_host].events.fire_event("s2sout-established", event_data);
122         else
123                 prosody.events.fire_event("s2sin-established", event_data);
124                 hosts[session.to_host].events.fire_event("s2sin-established", event_data);
125         end
126         
127         if session.direction == "outgoing" then
128                 if sendq then
129                         session.log("debug", "sending "..#sendq.." queued stanzas across new outgoing connection to "..session.to_host);
130                         for i, data in ipairs(sendq) do
131                                 send(data[1]);
132                                 sendq[i] = nil;
133                         end
134                         session.sendq = nil;
135                 end
136                 
137                 session.ip_hosts = nil;
138                 session.srv_hosts = nil;
139         end
140 end
141
142 local resting_session = { -- Resting, not dead
143                 destroyed = true;
144                 type = "s2s_destroyed";
145                 open_stream = function (session)
146                         session.log("debug", "Attempt to open stream on resting session");
147                 end;
148                 close = function (session)
149                         session.log("debug", "Attempt to close already-closed session");
150                 end;
151                 filter = function (type, data) return data; end;
152         }; resting_session.__index = resting_session;
153
154 function retire_session(session, reason)
155         local log = session.log or log;
156         for k in pairs(session) do
157                 if k ~= "trace" and k ~= "log" and k ~= "id" then
158                         session[k] = nil;
159                 end
160         end
161
162         session.destruction_reason = reason;
163
164         function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
165         function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
166         return setmetatable(session, resting_session);
167 end
168
169 function destroy_session(session, reason)
170         if session.destroyed then return; end
171         (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or ""));
172         
173         if session.direction == "outgoing" then
174                 hosts[session.from_host].s2sout[session.to_host] = nil;
175                 session:bounce_sendq(reason);
176         elseif session.direction == "incoming" then
177                 incoming_s2s[session] = nil;
178         end
179         
180         local event_data = { session = session, reason = reason };
181         if session.type == "s2sout" then
182                 prosody.events.fire_event("s2sout-destroyed", event_data);
183                 if hosts[session.from_host] then
184                         hosts[session.from_host].events.fire_event("s2sout-destroyed", event_data);
185                 end
186         elseif session.type == "s2sin" then
187                 prosody.events.fire_event("s2sin-destroyed", event_data);
188                 if hosts[session.to_host] then
189                         hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data);
190                 end
191         end
192         
193         retire_session(session, reason); -- Clean session until it is GC'd
194         return true;
195 end
196
197 return _M;