e27f8657b2a4b24b7fb90321cac49ae6ed5af7c3
[prosody.git] / plugins / mod_dialback.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 local hosts = _G.hosts;
11 local send_s2s = require "core.s2smanager".send_to_host;
12 local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
13 local s2s_initiate_dialback = require "core.s2smanager".initiate_dialback;
14 local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
15
16 local log = module._log;
17
18 local st = require "util.stanza";
19
20 local xmlns_stream = "http://etherx.jabber.org/streams";
21 local xmlns_dialback = "jabber:server:dialback";
22
23 local dialback_requests = setmetatable({}, { __mode = 'v' });
24
25 module:hook("stanza/jabber:server:dialback:verify", function(event)
26         local origin, stanza = event.origin, event.stanza;
27         
28         if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
29                 -- We are being asked to verify the key, to ensure it was generated by us
30                 origin.log("debug", "verifying that dialback key is ours...");
31                 local attr = stanza.attr;
32                 -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34
33                 --if attr.from ~= origin.to_host then error("invalid-from"); end
34                 local type;
35                 if s2s_verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
36                         type = "valid"
37                 else
38                         type = "invalid"
39                         origin.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to);
40                 end
41                 origin.log("debug", "verified dialback key... it is %s", type);
42                 origin.sends2s(st.stanza("db:verify", { from = attr.to, to = attr.from, id = attr.id, type = type }):text(stanza[1]));
43                 return true;
44         end
45 end);
46
47 module:hook("stanza/jabber:server:dialback:result", function(event)
48         local origin, stanza = event.origin, event.stanza;
49         
50         if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
51                 -- he wants to be identified through dialback
52                 -- We need to check the key with the Authoritative server
53                 local attr = stanza.attr;
54                 origin.hosts[attr.from] = { dialback_key = stanza[1] };
55                 
56                 if not hosts[attr.to] then
57                         -- Not a host that we serve
58                         origin.log("info", "%s tried to connect to %s, which we don't serve", attr.from, attr.to);
59                         origin:close("host-unknown");
60                         return true;
61                 end
62                 
63                 dialback_requests[attr.from.."/"..origin.streamid] = origin;
64                 
65                 if not origin.from_host then
66                         -- Just used for friendlier logging
67                         origin.from_host = attr.from;
68                 end
69                 if not origin.to_host then
70                         -- Just used for friendlier logging
71                         origin.to_host = attr.to;
72                 end
73                 
74                 origin.log("debug", "asking %s if key %s belongs to them", attr.from, stanza[1]);
75                 send_s2s(attr.to, attr.from,
76                         st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
77                 return true;
78         end
79 end);
80
81 module:hook("stanza/jabber:server:dialback:verify", function(event)
82         local origin, stanza = event.origin, event.stanza;
83         
84         if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
85                 local attr = stanza.attr;
86                 local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")];
87                 if dialback_verifying and attr.from == origin.to_host then
88                         local valid;
89                         if attr.type == "valid" then
90                                 s2s_make_authenticated(dialback_verifying, attr.from);
91                                 valid = "valid";
92                         else
93                                 -- Warn the original connection that is was not verified successfully
94                                 log("warn", "authoritative server for "..(attr.from or "(unknown)").." denied the key");
95                                 valid = "invalid";
96                         end
97                         if not dialback_verifying.sends2s then
98                                 log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the db result", tostring(dialback_verifying):match("%w+$"));
99                         else
100                                 dialback_verifying.sends2s(
101                                                 st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid })
102                                                                 :text(dialback_verifying.hosts[attr.from].dialback_key));
103                         end
104                         dialback_requests[attr.from.."/"..(attr.id or "")] = nil;
105                 end
106                 return true;
107         end
108 end);
109
110 module:hook("stanza/jabber:server:dialback:result", function(event)
111         local origin, stanza = event.origin, event.stanza;
112         
113         if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
114                 -- Remote server is telling us whether we passed dialback
115                 
116                 local attr = stanza.attr;
117                 if not hosts[attr.to] then
118                         origin:close("host-unknown");
119                         return true;
120                 elseif hosts[attr.to].s2sout[attr.from] ~= origin then
121                         -- This isn't right
122                         origin:close("invalid-id");
123                         return true;
124                 end
125                 if stanza.attr.type == "valid" then
126                         s2s_make_authenticated(origin, attr.from);
127                 else
128                         origin:close("not-authorized", "dialback authentication failed");
129                 end
130                 return true;
131         end
132 end);
133
134 module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
135         if origin.external_auth == "failed" then
136                 module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
137                 s2s_initiate_dialback(origin);
138                 return true;
139         end
140 end, 100);
141
142 module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
143         if not origin.external_auth or origin.external_auth == "failed" then
144                 s2s_initiate_dialback(origin);
145                 return true;
146         end
147 end, 100);
148
149 -- Offer dialback to incoming hosts
150 module:hook("s2s-stream-features", function (data)
151         data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up();
152 end);