diff options
Diffstat (limited to 'util')
-rw-r--r-- | util/events.lua | 39 | ||||
-rw-r--r-- | util/jid.lua | 12 | ||||
-rw-r--r-- | util/muc.lua | 422 | ||||
-rw-r--r-- | util/sasl.lua | 73 | ||||
-rw-r--r-- | util/serialization.lua | 2 | ||||
-rw-r--r-- | util/stanza.lua | 3 |
6 files changed, 493 insertions, 58 deletions
diff --git a/util/events.lua b/util/events.lua index b1f3811c..3816f30b 100644 --- a/util/events.lua +++ b/util/events.lua @@ -2,6 +2,7 @@ local ipairs = ipairs;
local pairs = pairs;
local t_insert = table.insert;
+local t_sort = table.sort;
local select = select;
module "events"
@@ -10,32 +11,32 @@ function new() local dispatchers = {};
local handlers = {};
local event_map = {};
- local function _rebuild_index() -- TODO optimize index rebuilding
- for event, _handlers in pairs(event_map) do
- local index = handlers[event];
- if index then
- for i=#index,1,-1 do index[i] = nil; end
- else index = {}; handlers[event] = index; end
- for handler in pairs(_handlers) do
- t_insert(index, handler);
- end
+ local function _rebuild_index(event) -- TODO optimize index rebuilding
+ local _handlers = event_map[event];
+ local index = handlers[event];
+ if index then
+ for i=#index,1,-1 do index[i] = nil; end
+ else index = {}; handlers[event] = index; end
+ for handler in pairs(_handlers) do
+ t_insert(index, handler);
end
+ t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
end;
- local function add_handler(event, handler)
+ local function add_handler(event, handler, priority)
local map = event_map[event];
if map then
- map[handler] = true;
+ map[handler] = priority or 0;
else
- map = {[handler] = true};
+ map = {[handler] = priority or 0};
event_map[event] = map;
end
- _rebuild_index();
+ _rebuild_index(event);
end;
local function remove_handler(event, handler)
local map = event_map[event];
if map then
map[handler] = nil;
- _rebuild_index();
+ _rebuild_index(event);
end
end;
local function add_plugin(plugin)
@@ -51,9 +52,10 @@ function new() local function _create_dispatcher(event) -- FIXME duplicate code in fire_event
local h = handlers[event];
if not h then h = {}; handlers[event] = h; end
- local dispatcher = function(data)
+ local dispatcher = function(...)
for _, handler in ipairs(h) do
- handler(data);
+ local ret = handler(...);
+ if ret ~= nil then return ret; end
end
end;
dispatchers[event] = dispatcher;
@@ -62,11 +64,12 @@ function new() local function get_dispatcher(event)
return dispatchers[event] or _create_dispatcher(event);
end;
- local function fire_event(event, data) -- FIXME duplicates dispatcher code
+ local function fire_event(event, ...) -- FIXME duplicates dispatcher code
local h = handlers[event];
if h then
for _, handler in ipairs(h) do
- handler(data);
+ local ret = handler(...);
+ if ret ~= nil then return ret; end
end
end
end;
diff --git a/util/jid.lua b/util/jid.lua index b6baf9dd..4f8b6d41 100644 --- a/util/jid.lua +++ b/util/jid.lua @@ -15,7 +15,7 @@ local resourceprep = require "util.encodings".stringprep.resourceprep; module "jid" -function split(jid) +local function _split(jid) if not jid then return; end local node, nodepos = match(jid, "^([^@]+)@()"); local host, hostpos = match(jid, "^([^@/]+)()", nodepos) @@ -24,17 +24,18 @@ function split(jid) if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end return node, host, resource; end +split = _split; function bare(jid) - local node, host = split(jid); + local node, host = _split(jid); if node and host then return node.."@"..host; end return host; end -function prepped_split(jid) - local node, host, resource = split(jid); +local function _prepped_split(jid) + local node, host, resource = _split(jid); if host then host = nameprep(host); if not host then return; end @@ -49,9 +50,10 @@ function prepped_split(jid) return node, host, resource; end end +prepped_split = _prepped_split; function prep(jid) - local node, host, resource = prepped_split(jid); + local node, host, resource = _prepped_split(jid); if host then if node then host = node .. "@" .. host; diff --git a/util/muc.lua b/util/muc.lua new file mode 100644 index 00000000..305157d8 --- /dev/null +++ b/util/muc.lua @@ -0,0 +1,422 @@ +-- Prosody IM v0.4 +-- Copyright (C) 2008-2009 Matthew Wild +-- Copyright (C) 2008-2009 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local datamanager = require "util.datamanager"; +local datetime = require "util.datetime"; + +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; +local st = require "util.stanza"; +local log = require "util.logger".init("mod_muc"); +local multitable_new = require "util.multitable".new; +local t_insert, t_remove = table.insert, table.remove; + +local muc_domain = nil; --module:get_host(); +local history_length = 20; + +------------ +local function filter_xmlns_from_array(array, filters) + local count = 0; + for i=#array,1,-1 do + local attr = array[i].attr; + if filters[attr and attr.xmlns] then + t_remove(array, i); + count = count + 1; + end + end + return count; +end +local function filter_xmlns_from_stanza(stanza, filters) + if filters then + if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then + return stanza, filter_xmlns_from_array(stanza, filters); + end + end + return stanza, 0; +end +local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; +local function get_filtered_presence(stanza) + return filter_xmlns_from_stanza(st.clone(stanza), presence_filters); +end +local kickable_error_conditions = { + ["gone"] = true; + ["internal-server-error"] = true; + ["item-not-found"] = true; + ["jid-malformed"] = true; + ["recipient-unavailable"] = true; + ["redirect"] = true; + ["remote-server-not-found"] = true; + ["remote-server-timeout"] = true; + ["service-unavailable"] = true; +}; +local function get_kickable_error(stanza) + for _, tag in ipairs(stanza.tags) do + if tag.name == "error" and tag.attr.xmlns == "jabber:client" then + for _, cond in ipairs(tag.tags) do + if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then + return kickable_error_conditions[cond.name] and cond.name; + end + end + return true; -- malformed error message + end + end + return true; -- malformed error message +end +local function getUsingPath(stanza, path, getText) + local tag = stanza; + for _, name in ipairs(path) do + if type(tag) ~= 'table' then return; end + tag = tag:child_with_name(name); + end + if tag and getText then tag = table.concat(tag); end + return tag; +end +local function getTag(stanza, path) return getUsingPath(stanza, path); end +local function getText(stanza, path) return getUsingPath(stanza, path, true); end +----------- + +--[[function get_room_disco_info(room, stanza) + return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") + :tag("identity", {category='conference', type='text', name=room._data["name"]):up() + :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply +end +function get_room_disco_items(room, stanza) + return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); +end -- TODO allow non-private rooms]] + +-- + +local function room_broadcast_presence(room, stanza, code, nick) + stanza = get_filtered_presence(stanza); + local data = room._participants[stanza.attr.from]; + stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up(); + if code then + stanza:tag("status", {code=code}):up(); + end + local me; + for occupant, o_data in pairs(room._participants) do + if occupant ~= stanza.attr.from then + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + room:route_stanza(stanza); + end + else + me = o_data; + end + end + if me then + stanza:tag("status", {code='110'}); + for jid in pairs(me.sessions) do + stanza.attr.to = jid; + room:route_stanza(stanza); + end + end +end +local function room_broadcast_message(room, stanza, historic) + for occupant, o_data in pairs(room._participants) do + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + room:route_stanza(stanza); + end + end + if historic then -- add to history + local history = room._data['history']; + if not history then history = {}; room._data['history'] = history; end + -- stanza = st.clone(stanza); + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + t_insert(history, st.clone(st.preserialize(stanza))); + while #history > history_length do t_remove(history, 1) end + end +end + + +local function room_send_occupant_list(room, to) + local current_nick = room._jid_nick[to]; + for occupant, o_data in pairs(room._participants) do + if occupant ~= current_nick then + local pres = get_filtered_presence(o_data.sessions[o_data.jid]); + pres.attr.to, pres.attr.from = to, occupant; + pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up(); + room:route_stanza(pres); + end + end +end +local function room_send_history(room, to) + local history = room._data['history']; -- send discussion history + if history then + for _, msg in ipairs(history) do + msg = st.deserialize(msg); + msg.attr.to=to; + room:route_stanza(msg); + end + end + if room._data['subject'] then + room:route_stanza(st.message({type='groupchat', from=room, to=to}):tag("subject"):text(room._data['subject'])); + end +end + +local function room_get_disco_info(self, stanza) end +local function room_get_disco_items(self, stanza) end +local function room_set_subject(room, current_nick, room, subject) + -- TODO check nick's authority + if subject == "" then subject = nil; end + room._data['subject'] = subject; + local msg = st.message({type='groupchat', from=current_nick}) + :tag('subject'):text(subject):up(); + room_broadcast_message(room, msg, false); + return true; +end + +local function room_handle_to_occupant(self, origin, stanza) -- PM, vCards, etc + local from, to = stanza.attr.from, stanza.attr.to; + local room = jid_bare(to); + local current_nick = self._jid_nick[from]; + local type = stanza.attr.type; + log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); + if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end + if stanza.name == "presence" then + local pr = get_filtered_presence(stanza); + pr.attr.from = current_nick; + if type == "error" then -- error, kick em out! + if current_nick then + log("debug", "kicking %s from %s", current_nick, room); + room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable + end + elseif type == "unavailable" then -- unavailable + if current_nick then + log("debug", "%s leaving %s", current_nick, room); + local data = self._participants[current_nick]; + data.role = 'none'; + room_broadcast_presence(room, pr); + self._participants[current_nick] = nil; + self._jid_nick[from] = nil; + end + elseif not type then -- available + if current_nick then + --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence + if current_nick == to then -- simple presence + log("debug", "%s broadcasted presence", current_nick); + self._participants[current_nick].sessions[from] = pr; + room_broadcast_presence(self, pr); + else -- change nick + if self._participants[to] then + log("debug", "%s couldn't change nick", current_nick); + origin.send(st.error_reply(stanza, "cancel", "conflict")); + else + local data = self._participants[current_nick]; + local to_nick = select(3, jid_split(to)); + if to_nick then + log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + local p = st.presence({type='unavailable', from=current_nick}); + room_broadcast_presence(self, p, '303', to_nick); + self._participants[current_nick] = nil; + self._participants[to] = data; + self._jid_nick[from] = to; + pr.attr.from = to; + self._participants[to].sessions[from] = pr; + room_broadcast_presence(self, pr); + else + --TODO malformed-jid + end + end + end + --else -- possible rejoin + -- log("debug", "%s had connection replaced", current_nick); + -- handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable + -- handle_to_occupant(origin, stanza); -- resend available + --end + else -- enter room + local new_nick = to; + if self._participants[to] then + new_nick = nil; + end + if not new_nick then + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + origin.send(st.error_reply(stanza, "cancel", "conflict")); + else + log("debug", "%s joining as %s", from, to); + local data; +-- if not rooms:get(room) and not rooms_info:get(room) then -- new room +-- rooms_info:set(room, 'name', (jid_split(room))); +-- data = {affiliation='owner', role='moderator', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; +-- end + if not data then -- new occupant + data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}}; + end + self._participants[to] = data; + self._jid_nick[from] = to; + room_send_occupant_list(self, from); + pr.attr.from = to; + room_broadcast_presence(self, pr); + room_send_history(self, from); + end + end + elseif type ~= 'result' then -- bad type + origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + end + elseif not current_nick and type ~= "error" then -- not in room + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM + origin.send(st.error_reply(stanza, "modify", "bad-request")); + elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then + log("debug", "%s kicked from %s for sending an error message", current_nick, room); + room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable + else -- private stanza + local o_data = self._participants[to]; + if o_data then + log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); + local jid = o_data.jid; + if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then jid = jid_bare(jid); end + stanza.attr.to, stanza.attr.from = jid, current_nick; + self:route_stanza(stanza); + elseif type ~= "error" and type ~= "result" then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + end + end +end + +local function room_handle_to_room(self, origin, stanza) -- presence changes and groupchat messages, along with disco/etc + local type = stanza.attr.type; + if stanza.name == "iq" and type == "get" then -- disco requests + local xmlns = stanza.tags[1].attr.xmlns; + if xmlns == "http://jabber.org/protocol/disco#info" then + origin.send(room_get_disco_info(self, stanza)); + elseif xmlns == "http://jabber.org/protocol/disco#items" then + origin.send(room_get_disco_items(self, stanza)); + else + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end + elseif stanza.name == "message" and type == "groupchat" then + local from, to = stanza.attr.from, stanza.attr.to; + local room = jid_bare(to); + local current_nick = self._jid_nick[from]; + if not current_nick then -- not in room + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + else + local from = stanza.attr.from; + stanza.attr.from = current_nick; + local subject = getText(stanza, {"subject"}); + if subject then + self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + else + room_broadcast_message(self, stanza, true); + end + end + elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick + local to = stanza.attr.to; + local current_nick = self._jid_nick[stanza.attr.from]; + if current_nick then + stanza.attr.to = current_nick; + room_handle_to_occupant(origin, stanza); + stanza.attr.to = to; + elseif type ~= "error" and type ~= "result" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end + elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] + and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" and #stanza.tags[1].tags == 1 + and stanza.tags[1].tags[1].name == "invite" and stanza.tags[1].tags[1].attr.to then + local _from, _to = stanza.attr.from, stanza.attr.to; + local _invitee = stanza.tags[1].tags[1].attr.to; + stanza.attr.from, stanza.attr.to = _to, _invitee; + stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = _from, nil; + self:route_stanza(stanza); + stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = nil, _invitee; + stanza.attr.from, stanza.attr.to = _from, _to; + else + if type == "error" or type == "result" then return; end + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +local function room_handle_stanza(self, origin, stanza) + local to_node, to_host, to_resource = jid_split(stanza.attr.to); + if to_resource and not to_node then + if type == "error" or type == "result" then return; end + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource + elseif to_resource then + room_handle_to_occupant(self, origin, stanza); + elseif to_node then + room_handle_to_room(self, origin, stanza) + else -- to the main muc domain + end +end + +module "muc" + +function new_room(jid) + return { + jid = jid; + handle_stanza = room_handle_stanza; + set_subject = room_set_subject; + route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end + _jid_nick = {}; + _participants = {}; + _data = {}; + } +end + +return _M; + +--[[function get_disco_info(stanza) + return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") + :tag("identity", {category='conference', type='text', name=muc_name}):up() + :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply +end +function get_disco_items(stanza) + local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); + for room in pairs(rooms_info:get()) do + reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up(); + end + return reply; -- TODO cache disco reply +end]] + +--[[function handle_to_domain(origin, stanza) + local type = stanza.attr.type; + if type == "error" or type == "result" then return; end + if stanza.name == "iq" and type == "get" then + local xmlns = stanza.tags[1].attr.xmlns; + if xmlns == "http://jabber.org/protocol/disco#info" then + origin.send(get_disco_info(stanza)); + elseif xmlns == "http://jabber.org/protocol/disco#items" then + origin.send(get_disco_items(stanza)); + else + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc + end + else + origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); + end +end + +register_component(muc_domain, function(origin, stanza) + local to_node, to_host, to_resource = jid_split(stanza.attr.to); + if to_resource and not to_node then + if type == "error" or type == "result" then return; end + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource + elseif to_resource then + handle_to_occupant(origin, stanza); + elseif to_node then + handle_to_room(origin, stanza) + else -- to the main muc domain + if type == "error" or type == "result" then return; end + handle_to_domain(origin, stanza); + end +end);]] + +--[[module.unload = function() + deregister_component(muc_domain); +end +module.save = function() + return {rooms = rooms.data; jid_nick = jid_nick.data; rooms_info = rooms_info.data; persist_list = persist_list}; +end +module.restore = function(data) + rooms.data, jid_nick.data, rooms_info.data, persist_list = + data.rooms or {}, data.jid_nick or {}, data.rooms_info or {}, data.persist_list or {}; +end]] diff --git a/util/sasl.lua b/util/sasl.lua index 2740b427..54715613 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -26,8 +26,6 @@ local math = require "math" local type = type local error = error local print = print -local idna_ascii = require "util.encodings".idna.to_ascii -local idna_unicode = require "util.encodings".idna.to_unicode module "sasl" @@ -62,8 +60,10 @@ local function new_plain(realm, password_handler) return object end + +-- implementing RFC 2831 local function new_digest_md5(realm, password_handler) - --TODO maybe support for authzid + --TODO complete support for authzid local function serialize(message) local data = "" @@ -131,29 +131,29 @@ local function new_digest_md5(realm, password_handler) local function parse(data) message = {} for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder - message[k] = v + message[k] = v; end - return message + return message; end - local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler} + local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler}; --TODO: something better than math.random would be nice, maybe OpenSSL's random number generator - object.nonce = generate_uuid() - object.step = 0 - object.nonce_count = {} + object.nonce = generate_uuid(); + object.step = 0; + object.nonce_count = {}; function object.feed(self, message) - self.step = self.step + 1 + self.step = self.step + 1; if (self.step == 1) then local challenge = serialize({ nonce = object.nonce, qop = "auth", charset = "utf-8", algorithm = "md5-sess", realm = self.realm}); - return "challenge", challenge + return "challenge", challenge; elseif (self.step == 2) then - local response = parse(message) + local response = parse(message); -- check for replay attack if response["nc"] then if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end @@ -161,13 +161,13 @@ local function new_digest_md5(realm, password_handler) -- check for username, it's REQUIRED by RFC 2831 if not response["username"] then - return "failure", "malformed-request" + return "failure", "malformed-request"; end - self["username"] = response["username"] + self["username"] = response["username"]; -- check for nonce, ... if not response["nonce"] then - return "failure", "malformed-request" + return "failure", "malformed-request"; else -- check if it's the right nonce if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end @@ -186,44 +186,53 @@ local function new_digest_md5(realm, password_handler) if response["charset"] == nil then decoder = utf8tolatin1ifpossible; elseif response["charset"] ~= "utf-8" then - return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8." + return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; end - local domain = "" - local protocol = "" + local domain = ""; + local protocol = ""; if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$") + protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); if protocol == nil or domain == nil then return "failure", "malformed-request" end else return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." end --TODO maybe realm support - self.username = response["username"] + self.username = response["username"]; local password_encoding, Y = self.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder) if Y == nil then return "failure", "not-authorized" elseif Y == false then return "failure", "account-disabled" end - - local A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid + local A1 = ""; + if response.authzid then + if response.authzid == self.username.."@"..self.realm then + log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; + else + A1 = "?"; + end + else + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; + end local A2 = "AUTHENTICATE:"..protocol.."/"..domain; - local HA1 = md5(A1, true) - local HA2 = md5(A2, true) + local HA1 = md5(A1, true); + local HA2 = md5(A2, true); - local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local response_value = md5(KD, true) + local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; + local response_value = md5(KD, true); if response_value == response["response"] then -- calculate rspauth A2 = ":"..protocol.."/"..domain; - HA1 = md5(A1, true) - HA2 = md5(A2, true) + HA1 = md5(A1, true); + HA2 = md5(A2, true); KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local rspauth = md5(KD, true) - self.authenticated = true - return "challenge", serialize({rspauth = rspauth}) + local rspauth = md5(KD, true); + self.authenticated = true; + return "challenge", serialize({rspauth = rspauth}); else return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." end @@ -232,7 +241,7 @@ local function new_digest_md5(realm, password_handler) else return "failure", "malformed-request" end end end - return object + return object; end local function new_anonymous(realm, password_handler) diff --git a/util/serialization.lua b/util/serialization.lua index 4da811ae..1ffd3e16 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -53,7 +53,7 @@ local function _simplesave(o, ind, t, func) func(t, (o and "true" or "false")); else log("error", "cannot serialize a %s: %s", type(o), debug_traceback()) - func(t, "nil,\n"); + func(t, "nil"); end end diff --git a/util/stanza.lua b/util/stanza.lua index 7e40dfa4..526bb2f0 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -258,10 +258,9 @@ function reply(orig) return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) }); end -function error_reply(orig, type, condition, message, clone) +function error_reply(orig, type, condition, message) local t = reply(orig); t.attr.type = "error"; - -- TODO use clone t:tag("error", {type = type}) :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); if (message) then t:tag("text"):text(message):up(); end |