aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/sasl.lua21
-rw-r--r--util/sasl/anonymous.lua12
-rw-r--r--util/sasl/digest-md5.lua17
-rw-r--r--util/sasl/plain.lua28
-rw-r--r--util/sasl/scram.lua144
-rw-r--r--util/xmppstream.lua168
6 files changed, 307 insertions, 83 deletions
diff --git a/util/sasl.lua b/util/sasl.lua
index eb71956b..306acc0c 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -41,27 +41,6 @@ Authentication Backend Prototypes:
state = false : disabled
state = true : enabled
state = nil : non-existant
-
-plain:
- function(username, realm)
- return password, state;
- end
-
-plain-test:
- function(username, realm, password)
- return true or false, state;
- end
-
-digest-md5:
- function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
- -- implementations it's not
- return digesthash, state;
- end
-
-digest-md5-test:
- function(username, domain, realm, encoding, digesthash)
- return true or false, state;
- end
]]
local method = {};
diff --git a/util/sasl/anonymous.lua b/util/sasl/anonymous.lua
index 65650294..f3e31a7f 100644
--- a/util/sasl/anonymous.lua
+++ b/util/sasl/anonymous.lua
@@ -1,5 +1,5 @@
-- sasl.lua v0.4
--- Copyright (C) 2008-2009 Tobias Markmann
+-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
@@ -20,6 +20,16 @@ module "anonymous"
--=========================
--SASL ANONYMOUS according to RFC 4505
+
+--[[
+Supported Authentication Backends
+
+anonymous:
+ function(username, realm)
+ return true; --for normal usage just return true; if you don't like the supplied username you can return false.
+ end
+]]
+
local function anonymous(self, message)
local username;
repeat
diff --git a/util/sasl/digest-md5.lua b/util/sasl/digest-md5.lua
index 04acf04d..8986ca45 100644
--- a/util/sasl/digest-md5.lua
+++ b/util/sasl/digest-md5.lua
@@ -1,5 +1,5 @@
-- sasl.lua v0.4
--- Copyright (C) 2008-2009 Tobias Markmann
+-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
@@ -29,6 +29,21 @@ module "digest-md5"
--=========================
--SASL DIGEST-MD5 according to RFC 2831
+--[[
+Supported Authentication Backends
+
+digest-md5:
+ function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
+ -- implementations it's not
+ return digesthash, state;
+ end
+
+digest-md5-test:
+ function(username, domain, realm, encoding, digesthash)
+ return true or false, state;
+ end
+]]
+
local function digest(self, message)
--TODO complete support for authzid
diff --git a/util/sasl/plain.lua b/util/sasl/plain.lua
index ae5c777a..2abbc53a 100644
--- a/util/sasl/plain.lua
+++ b/util/sasl/plain.lua
@@ -1,5 +1,5 @@
-- sasl.lua v0.4
--- Copyright (C) 2008-2009 Tobias Markmann
+-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
@@ -19,6 +19,26 @@ module "plain"
-- ================================
-- SASL PLAIN according to RFC 4616
+
+--[[
+Supported Authentication Backends
+
+plain:
+ function(username, realm)
+ return password, state;
+ end
+
+plain-test:
+ function(username, realm, password)
+ return true or false, state;
+ end
+
+plain-hashed:
+ function(username, realm)
+ return hashed_password, hash_function, state;
+ end
+]]
+
local function plain(self, message)
if not message then
return "failure", "malformed-request";
@@ -46,6 +66,10 @@ local function plain(self, message)
if correct_password == password then correct = true; else correct = false; end
elseif self.profile.plain_test then
correct, state = self.profile.plain_test(authentication, self.realm, password);
+ elseif self.profile.plain_hashed then
+ local hashed_password, hash_f;
+ hashed_password, hash_f, state = self.profile.plain_hashed(authentication, self.realm);
+ if hashed_password == hash_f(password) then correct = true; else correct = false; end
end
self.username = authentication
@@ -61,7 +85,7 @@ local function plain(self, message)
end
function init(registerMechanism)
- registerMechanism("PLAIN", {"plain", "plain_test"}, plain);
+ registerMechanism("PLAIN", {"plain", "plain_test", "plain_hashed"}, plain);
end
return _M;
diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua
index 103e8a90..ed7d7bc3 100644
--- a/util/sasl/scram.lua
+++ b/util/sasl/scram.lua
@@ -1,5 +1,5 @@
-- sasl.lua v0.4
--- Copyright (C) 2008-2009 Tobias Markmann
+-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
@@ -28,6 +28,16 @@ module "scram"
--=========================
--SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
+
+--[[
+Supported Authentication Backends
+
+scram-{MECH}:
+ function(username, realm)
+ return salted_password, iteration_count, salt, state;
+ end
+]]
+
local default_i = 4096
local function bp( b )
@@ -82,77 +92,95 @@ local function validate_username(username)
return username;
end
-local function scram_sha_1(self, message)
- if not self.state then self["state"] = {} end
+local function scram_gen(hash_name, H_f, HMAC_f)
+ local function scram_hash(self, message)
+ if not self.state then self["state"] = {} end
- if not self.state.name then
- -- we are processing client_first_message
- local client_first_message = message;
- self.state["client_first_message"] = client_first_message;
- self.state["name"] = client_first_message:match("n=(.+),r=")
- self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
-
- if not self.state.name or not self.state.clientnonce then
- return "failure", "malformed-request";
- end
-
- self.state.name = validate_username(self.state.name);
if not self.state.name then
- log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
- return "failure", "malformed-request", "Invalid username.";
- end
+ -- we are processing client_first_message
+ local client_first_message = message;
+ self.state["client_first_message"] = client_first_message;
+ self.state["name"] = client_first_message:match("n=(.+),r=")
+ self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
- self.state["servernonce"] = generate_uuid();
- self.state["salt"] = generate_uuid();
+ if not self.state.name or not self.state.clientnonce then
+ return "failure", "malformed-request";
+ end
- local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i;
- self.state["server_first_message"] = server_first_message;
- return "challenge", server_first_message
- else
- if type(message) ~= "string" then return "failure", "malformed-request" end
- -- we are processing client_final_message
- local client_final_message = message;
+ self.state.name = validate_username(self.state.name);
+ if not self.state.name then
+ log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
+ return "failure", "malformed-request", "Invalid username.";
+ end
- self.state["proof"] = client_final_message:match("p=(.+)");
- self.state["nonce"] = client_final_message:match("r=(.+),p=");
- self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
- if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
- return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
- end
+ self.state["servernonce"] = generate_uuid();
+
+ -- retreive credentials
+ if self.profile.plain then
+ local password, state = self.profile.plain(self.state.name, self.realm)
+ if state == nil then return "failure", "not-authorized"
+ elseif state == false then return "failure", "account-disabled" end
+
+ password = saslprep(password);
+ if not password then
+ log("debug", "Password violates SASLprep.");
+ return "failure", "not-authorized", "Invalid password."
+ end
+ self.state.salt = generate_uuid();
+ self.state.iteration_count = default_i;
+ self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i);
+ elseif self.profile["scram_"..hash_name] then
+ local salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm);
+ if state == nil then return "failure", "not-authorized"
+ elseif state == false then return "failure", "account-disabled" end
+
+ self.state.salted_password = salted_password;
+ self.state.iteration_count = iteration_count;
+ self.state.salt = salt
+ end
- local password, state;
- if self.profile.plain then
- password, state = self.profile.plain(self.state.name, self.realm)
- if state == nil then return "failure", "not-authorized"
- elseif state == false then return "failure", "account-disabled" end
- password = saslprep(password);
- if not password then
- log("debug", "Password violates SASLprep.");
- return "failure", "not-authorized", "Invalid password."
+ local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count;
+ self.state["server_first_message"] = server_first_message;
+ return "challenge", server_first_message
+ else
+ if type(message) ~= "string" then return "failure", "malformed-request" end
+ -- we are processing client_final_message
+ local client_final_message = message;
+
+ self.state["proof"] = client_final_message:match("p=(.+)");
+ self.state["nonce"] = client_final_message:match("r=(.+),p=");
+ self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
+ if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
+ return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
end
- end
- local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i)
- local ClientKey = hmac_sha1(SaltedPassword, "Client Key")
- local ServerKey = hmac_sha1(SaltedPassword, "Server Key")
- local StoredKey = sha1(ClientKey)
- local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
- local ClientSignature = hmac_sha1(StoredKey, AuthMessage)
- local ClientProof = binaryXOR(ClientKey, ClientSignature)
- local ServerSignature = hmac_sha1(ServerKey, AuthMessage)
+ local SaltedPassword = self.state.salted_password;
+ local ClientKey = HMAC_f(SaltedPassword, "Client Key")
+ local ServerKey = HMAC_f(SaltedPassword, "Server Key")
+ local StoredKey = H_f(ClientKey)
+ local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
+ local ClientSignature = HMAC_f(StoredKey, AuthMessage)
+ local ClientProof = binaryXOR(ClientKey, ClientSignature)
+ local ServerSignature = HMAC_f(ServerKey, AuthMessage)
- if base64.encode(ClientProof) == self.state.proof then
- local server_final_message = "v="..base64.encode(ServerSignature);
- self["username"] = self.state.name;
- return "success", server_final_message;
- else
- return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
+ if base64.encode(ClientProof) == self.state.proof then
+ local server_final_message = "v="..base64.encode(ServerSignature);
+ self["username"] = self.state.name;
+ return "success", server_final_message;
+ else
+ return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
+ end
end
end
+ return scram_hash;
end
function init(registerMechanism)
- registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1);
+ local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
+ registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash));
+ end
+
+ registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
end
return _M; \ No newline at end of file
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
new file mode 100644
index 00000000..ed5395b5
--- /dev/null
+++ b/util/xmppstream.lua
@@ -0,0 +1,168 @@
+-- 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 lxp = require "lxp";
+local st = require "util.stanza";
+
+local tostring = tostring;
+local t_insert = table.insert;
+local t_concat = table.concat;
+
+local default_log = require "util.logger".init("xmlhandlers");
+
+local error = error;
+
+module "xmppstream"
+
+local new_parser = lxp.new;
+
+local ns_prefixes = {
+ ["http://www.w3.org/XML/1998/namespace"] = "xml";
+};
+
+local xmlns_streams = "http://etherx.jabber.org/streams";
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+
+function new_sax_handlers(session, stream_callbacks)
+ local xml_handlers = {};
+
+ local log = session.log or default_log;
+
+ local cb_streamopened = stream_callbacks.streamopened;
+ local cb_streamclosed = stream_callbacks.streamclosed;
+ local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
+ local cb_handlestanza = stream_callbacks.handlestanza;
+
+ local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
+ local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
+ local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
+
+ local stream_default_ns = stream_callbacks.default_ns;
+
+ local chardata, stanza = {};
+ function xml_handlers:StartElement(tagname, attr)
+ if stanza and #chardata > 0 then
+ -- We have some character data in the buffer
+ stanza:text(t_concat(chardata));
+ chardata = {};
+ end
+ local curr_ns,name = tagname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+
+ if curr_ns ~= stream_default_ns then
+ attr.xmlns = curr_ns;
+ end
+
+ -- FIXME !!!!!
+ for i=1,#attr do
+ local k = attr[i];
+ attr[i] = nil;
+ local ns, nm = k:match(ns_pattern);
+ if nm ~= "" then
+ ns = ns_prefixes[ns];
+ if ns then
+ attr[ns..":"..nm] = attr[k];
+ attr[k] = nil;
+ end
+ end
+ end
+
+ if not stanza then --if we are not currently inside a stanza
+ if session.notopen then
+ if tagname == stream_tag then
+ if cb_streamopened then
+ cb_streamopened(session, attr);
+ end
+ else
+ -- Garbage before stream?
+ cb_error(session, "no-stream");
+ end
+ return;
+ end
+ if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
+ cb_error(session, "invalid-top-level-element");
+ end
+
+ stanza = st.stanza(name, attr);
+ else -- we are inside a stanza, so add a tag
+ attr.xmlns = nil;
+ if curr_ns ~= stream_default_ns then
+ attr.xmlns = curr_ns;
+ end
+ stanza:tag(name, attr);
+ end
+ end
+ function xml_handlers:CharacterData(data)
+ if stanza then
+ t_insert(chardata, data);
+ end
+ end
+ function xml_handlers:EndElement(tagname)
+ if stanza then
+ if #chardata > 0 then
+ -- We have some character data in the buffer
+ stanza:text(t_concat(chardata));
+ chardata = {};
+ end
+ -- Complete stanza
+ if #stanza.last_add == 0 then
+ if tagname ~= stream_error_tag then
+ cb_handlestanza(session, stanza);
+ else
+ cb_error(session, "stream-error", stanza);
+ end
+ stanza = nil;
+ else
+ stanza:up();
+ end
+ else
+ if tagname == stream_tag then
+ if cb_streamclosed then
+ cb_streamclosed(session);
+ end
+ else
+ local curr_ns,name = tagname:match(ns_pattern);
+ if name == "" then
+ curr_ns, name = "", curr_ns;
+ end
+ cb_error(session, "parse-error", "unexpected-element-close", name);
+ end
+ stanza, chardata = nil, {};
+ end
+ end
+
+ local function reset()
+ stanza, chardata = nil, {};
+ end
+
+ return xml_handlers, { reset = reset };
+end
+
+function new(session, stream_callbacks)
+ local handlers, meta = new_sax_handlers(session, stream_callbacks);
+ local parser = new_parser(handlers, ns_separator);
+ local parse = parser.parse;
+
+ return {
+ reset = function ()
+ parser = new_parser(handlers, ns_separator);
+ parse = parser.parse;
+ meta.reset();
+ end,
+ feed = function (self, data)
+ return parse(parser, data);
+ end
+ };
+end
+
+return _M;