75af5eb69cfae06ff5ccc059106fc54ec59c3640
[prosody.git] / plugins / mod_saslauth.lua
1
2 local st = require "util.stanza";
3 local send = require "core.sessionmanager".send_to_session;
4 local sm_bind_resource = require "core.sessionmanager".bind_resource;
5 local jid
6
7 local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
8 local t_concat, t_insert = table.concat, table.insert;
9 local tostring = tostring;
10
11 local log = require "util.logger".init("mod_saslauth");
12
13 local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
14 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
15 local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
16
17 local new_sasl = require "util.sasl".new;
18
19 local function build_reply(status, ret)
20         local reply = st.stanza(status, {xmlns = xmlns_sasl});
21         if status == "challenge" then
22                 reply:text(ret or "");
23         elseif status == "failure" then
24                 reply:tag(ret):up();
25         elseif status == "success" then
26                 reply:text(ret or "");
27         else
28                 error("Unknown sasl status: "..status);
29         end
30         return reply;
31 end
32
33 local function handle_status(session, status)
34         if status == "failure" then
35                 session.sasl_handler = nil;
36         elseif status == "success" then
37                 session.sasl_handler = nil;
38                 session:reset_stream();
39         end
40 end
41
42 local function password_callback(jid, mechanism)
43         local node, host = jid_split(jid);
44         local password = (datamanager.load(node, host, "accounts") or {}).password; -- FIXME handle hashed passwords
45         local func = function(x) return x; end;
46         if password then
47                 if mechanism == "PLAIN" then
48                         return func, password;
49                 elseif mechanism == "DIGEST-MD5" then
50                         return func, require "hashes".md5(node.."::"..password);
51                 end
52         end
53         return func, nil;
54 end
55
56 function do_sasl(session, stanza)
57         local text = stanza[1];
58         if text then
59                 text = base64.decode(text);
60                 if not text then
61                         session.sasl_handler = nil;
62                         session.send(build_reply("failure", "incorrect-encoding"));
63                         return;
64                 end
65         end
66         local status, ret = session.sasl_handler:feed(text);
67         handle_status(session, status);
68         session.send(build_reply(status, ret));
69 end
70
71 add_handler("c2s_unauthed", "auth", xmlns_sasl,
72                 function (session, stanza)
73                         if not session.sasl_handler then
74                                 session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, password_callback);
75                                 do_sasl(session, stanza);
76                         else
77                                 error("Client tried to negotiate SASL again", 0);
78                         end
79                 end);
80
81 add_handler("c2s_unauthed", "abort", xmlns_sasl,
82         function(session, stanza)
83                 if not session.sasl_handler then error("Attempt to abort when sasl has not started"); end
84                 do_sasl(session, stanza);
85         end);
86
87 add_handler("c2s_unauthed", "response", xmlns_sasl,
88         function(session, stanza)
89                 if not session.sasl_handler then error("Attempt to respond when sasl has not started"); end
90                 do_sasl(session, stanza);
91         end);
92
93 add_event_hook("stream-features", 
94                                         function (session, features)                                                                                            
95                                                 if not session.username then
96                                                         t_insert(features, "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
97                                                                 t_insert(features, "<mechanism>PLAIN</mechanism>");
98                                                         t_insert(features, "</mechanisms>");
99                                                 else
100                                                         t_insert(features, "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>");
101                                                         t_insert(features, "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>");
102                                                 end
103                                                 --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]]
104                                         end);
105                                         
106 add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", 
107                 function (session, stanza)
108                         log("debug", "Client tried to bind to a resource");
109                         local resource;
110                         if stanza.attr.type == "set" then
111                                 local bind = stanza.tags[1];
112                                 
113                                 if bind and bind.attr.xmlns == xmlns_bind then
114                                         resource = bind:child_with_name("resource");
115                                         if resource then
116                                                 resource = resource[1];
117                                         end
118                                 end
119                         end
120                         local success, err = sm_bind_resource(session, resource);
121                         if not success then
122                                 local reply = st.reply(stanza);
123                                 reply.attr.type = "error";
124                                 if err == "conflict" then
125                                         reply:tag("error", { type = "modify" })
126                                                 :tag("conflict", { xmlns = xmlns_stanzas });
127                                 elseif err == "constraint" then
128                                         reply:tag("error", { type = "cancel" })
129                                                 :tag("resource-constraint", { xmlns = xmlns_stanzas });
130                                 elseif err == "auth" then
131                                         reply:tag("error", { type = "cancel" })
132                                                 :tag("not-allowed", { xmlns = xmlns_stanzas });
133                                 end
134                                 send(session, reply);
135                         else
136                                 local reply = st.reply(stanza);
137                                 reply:tag("bind", { xmlns = xmlns_bind})
138                                         :tag("jid"):text(session.full_jid);
139                                 send(session, reply);
140                         end
141                 end);
142                 
143 add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", 
144                 function (session, stanza)
145                         log("debug", "Client tried to bind to a resource");
146                         send(session, st.reply(stanza));
147                 end);