This commit is contained in:
Travis Burtrum 2015-11-25 13:42:19 -05:00
commit d0e23d516b
138 changed files with 23541 additions and 0 deletions

508
dev/mod_ircd.old_comments Normal file
View File

@ -0,0 +1,508 @@
-- README
-- Squish verse into this dir, then squish them into one, which you move
-- and rename to mod_ircd.lua in your prosody modules/plugins dir.
--
-- IRC spec:
-- http://tools.ietf.org/html/rfc2812
local _module = module
module = _G.module
local module = _module
--
local component_jid, component_secret, muc_server, port_number =
module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
if not muc_server then
module:log ("error", "You need to set the MUC server! halting.")
return false;
end
package.loaded["util.sha1"] = require "util.encodings";
local verse = require "verse"
require "verse.component"
require "socket"
c = verse.new();--verse.logger())
c:add_plugin("groupchat");
local function verse2prosody(e)
return c:event("stanza", e.stanza) or true;
end
module:hook("message/bare", verse2prosody);
module:hook("message/full", verse2prosody);
module:hook("presence/bare", verse2prosody);
module:hook("presence/full", verse2prosody);
c.type = "component";
c.send = core_post_stanza;
-- This plugin is actually a verse based component, but that mode is currently commented out
-- Add some hooks for debugging
--c:hook("opened", function () print("Stream opened!") end);
--c:hook("closed", function () print("Stream closed!") end);
--c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
--c:hook("incoming-raw", print, 1000);
--c:hook("stanza", print, 1000);
--c:hook("outgoing-raw", print, 1000);
-- Print a message after authentication
--c:hook("authentication-success", function () print("Logged in!"); end);
--c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
--c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
--c.connect_host = "127.0.0.1"
--c:connect_component(component_jid, component_secret);
local jid = require "util.jid";
local nodeprep = require "util.encodings".stringprep.nodeprep;
local function utf8_clean (s)
local push, join = table.insert, table.concat;
local r, i = {}, 1;
if not(s and #s > 0) then
return ""
end
while true do
local c = s:sub(i,i)
local b = c:byte();
local w = (
(b >= 9 and b <= 10 and 0) or
(b >= 32 and b <= 126 and 0) or
(b >= 192 and b <= 223 and 1) or
(b >= 224 and b <= 239 and 2) or
(b >= 240 and b <= 247 and 3) or
(b >= 248 and b <= 251 and 4) or
(b >= 251 and b <= 252 and 5) or nil
)
if not w then
push(r, "?")
else
local n = i + w;
if w == 0 then
push(r, c);
elseif n > #s then
push(r, ("?"):format(b));
else
local e = s:sub(i+1,n);
if e:match('^[\128-\191]*$') then
push(r, c);
push(r, e);
i = n;
else
push(r, ("?"):format(b));
end
end
end
i = i + 1;
if i > #s then
break
end
end
return join(r);
end
local function parse_line(line)
local ret = {};
if line:sub(1,1) == ":" then
ret.from, line = line:match("^:(%w+)%s+(.*)$");
end
for part in line:gmatch("%S+") do
if part:sub(1,1) == ":" then
ret[#ret+1] = line:match(":(.*)$");
break
end
ret[#ret+1]=part;
end
return ret;
end
local function build_line(parts)
if #parts > 1 then
parts[#parts] = ":" .. parts[#parts];
end
return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
end
local function irc2muc(channel, nick)
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
return jid.join(room, muc_server, nick)
end
local function muc2irc(room)
local channel, _, nick = jid.split(room);
return "#"..channel, nick;
end
local role_map = {
moderator = "@",
participant = "",
visitor = "",
none = ""
}
local aff_map = {
owner = "~",
administrator = "&",
member = "+",
none = ""
}
local role_modemap = {
moderator = "o",
participant = "",
visitor = "",
none = ""
}
local aff_modemap = {
owner = "q",
administrator = "a",
member = "v",
none = ""
}
local irc_listener = { default_port = port_number, default_mode = "*l" };
local sessions = {};
local jids = {};
local commands = {};
local nicks = {};
local st = require "util.stanza";
local conference_server = muc_server;
local function irc_close_session(session)
session.conn:close();
end
function irc_listener.onincoming(conn, data)
local session = sessions[conn];
if not session then
session = { conn = conn, host = component_jid, reset_stream = function () end,
close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
rooms = {},
roster = {} };
sessions[conn] = session;
function session.data(data)
local parts = parse_line(data);
module:log("debug", require"util.serialization".serialize(parts));
local command = table.remove(parts, 1);
if not command then
return;
end
command = command:upper();
if not session.nick then
if not (command == "USER" or command == "NICK") then
module:log("debug", "Client tried to send command %s before registering", command);
return session.send{from=muc_server, "451", command, "You have not registered"}
end
end
if commands[command] then
local ret = commands[command](session, parts);
if ret then
return session.send(ret);
end
else
session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
return module:log("debug", "Unknown command: %s", command);
end
end
function session.send(data)
if type(data) == "string" then
return conn:write(data.."\r\n");
elseif type(data) == "table" then
local line = build_line(data);
module:log("debug", line);
conn:write(line.."\r\n");
end
end
end
if data then
session.data(data);
end
end
function irc_listener.ondisconnect(conn, error)
local session = sessions[conn];
if session then
for _, room in pairs(session.rooms) do
room:leave("Disconnected");
end
if session.nick then
nicks[session.nick] = nil;
end
if session.full_jid then
jids[session.full_jid] = nil;
end
end
sessions[conn] = nil;
end
function commands.NICK(session, args)
if session.nick then
session.send{from = muc_server, "484", "*", nick, "I'm afraid I can't let you do that"};
--TODO Loop throug all rooms and change nick, with help from Verse.
return;
end
local nick = args[1];
nick = nick:gsub("[^%w_]","");
if nicks[nick] then
session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"};
return;
end
local full_jid = jid.join(nick, component_jid, "ircd");
jids[full_jid] = session;
jids[full_jid]["ar_last"] = {};
nicks[nick] = session;
session.nick = nick;
session.full_jid = full_jid;
session.type = "c2s";
session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick};
session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version};
session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)}
session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")};
session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"};
session.send{from = muc_server, "372", nick, "-"};
session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"};
session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."};
session.send{from = muc_server, "372", nick, "-"};
session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."};
session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"};
session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"};
session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."};
session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
-- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
end
function commands.USER(session, params)
-- FIXME
-- Empty command for now
end
local function mode_map(am, rm, nicks)
local rnick;
local c_modes;
c_modes = aff_modemap[am]..role_modemap[rm]
rnick = string.rep(nicks.." ", c_modes:len())
if c_modes == "" then return nil, nil end
return c_modes, rnick
end
function commands.JOIN(session, args)
local channel = args[1];
if not channel then return end
local room_jid = irc2muc(channel);
print(session.full_jid);
if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
if not room then
return ":"..muc_server.." ERR :Could not join room: "..err
end
session.rooms[channel] = room;
room.channel = channel;
room.session = session;
session.send{from=session.nick, "JOIN", channel};
if room.subject then
session.send{from=muc_server, 332, session.nick, channel ,room.subject};
end
commands.NAMES(session, channel);
room:hook("subject-changed", function(changed)
session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or ""));
end);
room:hook("message", function(event)
if not event.body then return end
local nick, body = event.nick, event.body;
if nick ~= session.nick then
if body:sub(1,4) == "/me " then
body = "\1ACTION ".. body:sub(5) .. "\1"
end
local type = event.stanza.attr.type;
session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body};
--FIXME PM's probably won't work
end
end);
room:hook("presence", function(ar)
local c_modes;
local rnick;
if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end
local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
if x_ar then
local xar_item = x_ar:get_child("item")
if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
if xar_item.attr.affiliation and xar_item.attr.role then
if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
else
c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick);
if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
end
end
end
end
end, -1);
end
c:hook("groupchat/joined", function(room)
local session = room.session or jids[room.opts.source];
local channel = "#"..room.jid:match("^(.*)@");
session.send{from=session.nick.."!"..session.nick, "JOIN", channel};
if room.topic then
session.send{from=muc_server, 332, room.topic};
end
commands.NAMES(session, channel)
room:hook("occupant-joined", function(nick)
session.send{from=nick.nick.."!"..nick.nick, "JOIN", channel};
end);
room:hook("occupant-left", function(nick)
jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; -- ugly
session.send{from=nick.nick.."!"..nick.nick, "PART", channel};
end);
end);
function commands.NAMES(session, channel)
local nicks = { };
local room = session.rooms[channel];
local symbols_map = {
owner = "~",
administrator = "&",
moderator = "@",
member = "+"
}
if not room then return end
-- TODO Break this out into commands.NAMES
for nick, n in pairs(room.occupants) do
if n.affiliation == "owner" and n.role == "moderator" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "administrator" and n.role == "moderator" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "member" and n.role == "moderator" then
nick = symbols_map[n.role]..nick;
elseif n.affiliation == "member" and n.role == "partecipant" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "none" and n.role == "moderator" then
nick = symbols_map[n.role]..nick;
end
table.insert(nicks, nick);
end
nicks = table.concat(nicks, " ");
session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks));
session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel));
session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks);
end
function commands.PART(session, args)
local channel, part_message = unpack(args);
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
if not room then return end
channel = channel:match("^([%S]*)");
session.rooms[channel]:leave(part_message);
jids[session.full_jid].ar_last[room.."@"..muc_server] = nil;
session.send(":"..session.nick.." PART :"..channel);
end
function commands.PRIVMSG(session, args)
local channel, message = unpack(args);
if message and #message > 0 then
if message:sub(1,8) == "\1ACTION " then
message = "/me ".. message:sub(9,-2)
end
message = utf8_clean(message);
if channel:sub(1,1) == "#" then
if session.rooms[channel] then
module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel);
session.rooms[channel]:send_message(message);
end
else -- private message
local nick = channel;
module:log("debug", "PM to %s", nick);
for channel, room in pairs(session.rooms) do
module:log("debug", "looking for %s in %s", nick, channel);
if room.occupants[nick] then
module:log("debug", "found %s in %s", nick, channel);
local who = room.occupants[nick];
-- FIXME PMs in verse
--room:send_private_message(nick, message);
local pm = st.message({type="chat",to=who.jid}, message);
module:log("debug", "sending PM to %s: %s", nick, tostring(pm));
room:send(pm)
break
end
end
end
end
end
function commands.PING(session, args)
session.send{from=muc_server, "PONG", args[1]};
end
function commands.TOPIC(session, message)
if not message then return end
local channel, topic = message[1], message[2];
channel = utf8_clean(channel);
topic = utf8_clean(topic);
if not channel then return end
local room = session.rooms[channel];
if topic then room:set_subject(topic); end
end
function commands.WHO(session, args)
local channel = args[1];
if session.rooms[channel] then
local room = session.rooms[channel]
for nick in pairs(room.occupants) do
--n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild
session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
end
session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
end
end
function commands.MODE(session, args) -- FIXME
-- emptied for the time being, until something sane which works is available.
end
function commands.QUIT(session, args)
session.send{"ERROR", "Closing Link: "..session.nick};
for _, room in pairs(session.rooms) do
room:leave(args[1]);
end
jids[session.full_jid] = nil;
nicks[session.nick] = nil;
sessions[session.conn] = nil;
session:close();
end
function commands.RAW(session, data)
--c:send(data)
end
local function desetup()
require "net.connlisteners".deregister("irc");
end
--c:hook("ready", function ()
require "net.connlisteners".register("irc", irc_listener);
require "net.connlisteners".start("irc");
--end);
module:hook("module-unloaded", desetup)
--print("Starting loop...")
--verse.loop()

8
ircd.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
set -e
squish
echo -e 'config:reload()\nmodule:reload("ircd", "burtrum.org")' | telnet localhost 5582
exit
echo -e 'module:load("ircd", "burtrum.org")' | telnet localhost 5582

598
mod_ircd.in.lua Normal file
View File

@ -0,0 +1,598 @@
-- README
-- Squish verse into this dir, then squish them into one, which you move
-- and rename to mod_ircd.lua in your prosody modules/plugins dir.
--
-- IRC spec:
-- http://tools.ietf.org/html/rfc2812
local client_xmlns = "jabber:client"
--module:set_global();
local server = require "net.server";
local portmanager = require "core.portmanager";
local component_jid, component_secret, muc_server, port_number =
module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
if not muc_server then
module:log ("error", "You need to set the MUC server in the configuration (conference_server)!")
module:log ("error", "Be a good boy or girl and go read the wiki at: http://code.google.com/p/prosody-modules/wiki/mod_ircd")
return false;
end
package.loaded["util.sha1"] = require "util.encodings";
--local verse = require "verse"
--require "verse.component"
--require "socket"
require "verse".init("component");
c = verse.new(); -- something interferes with prosody's console logging
c:add_plugin("groupchat");
local function verse2prosody(e)
return c:event("stanza", e.stanza) or true;
end
module:hook("message/bare", verse2prosody);
module:hook("message/full", verse2prosody);
module:hook("presence/bare", verse2prosody);
module:hook("presence/full", verse2prosody);
c.type = "component";
c.send = core_post_stanza;
local jid = require "util.jid";
local nodeprep = require "util.encodings".stringprep.nodeprep;
local function utf8_clean (s)
local push, join = table.insert, table.concat;
local r, i = {}, 1;
if not(s and #s > 0) then
return ""
end
while true do
local c = s:sub(i,i)
local b = c:byte();
local w = (
(b >= 9 and b <= 10 and 0) or
(b >= 32 and b <= 126 and 0) or
(b >= 192 and b <= 223 and 1) or
(b >= 224 and b <= 239 and 2) or
(b >= 240 and b <= 247 and 3) or
(b >= 248 and b <= 251 and 4) or
(b >= 251 and b <= 252 and 5) or nil
)
if not w then
push(r, "?")
else
local n = i + w;
if w == 0 then
push(r, c);
elseif n > #s then
push(r, ("?"):format(b));
else
local e = s:sub(i+1,n);
if e:match('^[\128-\191]*$') then
push(r, c);
push(r, e);
i = n;
else
push(r, ("?"):format(b));
end
end
end
i = i + 1;
if i > #s then
break
end
end
return join(r);
end
local function parse_line(line)
local ret = {};
if line:sub(1,1) == ":" then
ret.from, line = line:match("^:(%w+)%s+(.*)$");
end
for part in line:gmatch("%S+") do
if part:sub(1,1) == ":" then
ret[#ret+1] = line:match(":(.*)$");
break
end
ret[#ret+1]=part;
end
return ret;
end
local function build_line(parts)
if #parts > 1 then
parts[#parts] = ":" .. parts[#parts];
end
return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
end
local function irc2muc(channel, nick)
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
if not nick then
return jid.join(room, muc_server);
else
return jid.join(room, muc_server, nick);
end
end
local function muc2irc(room)
local channel, _, nick = jid.split(room);
return "#"..channel, nick;
end
local role_map = {
moderator = "@",
participant = "",
visitor = "",
none = ""
}
local aff_map = {
owner = "~",
administrator = "&",
member = "+",
none = ""
}
local role_modemap = {
moderator = "o",
participant = "",
visitor = "",
none = ""
}
local aff_modemap = {
owner = "q",
administrator = "a",
member = "v",
none = ""
}
local irc_listener = { default_port = port_number, default_mode = "*l"; interface = "192.168.1.3" };
local sessions = module:shared("sessions");
local jids = {};
local commands = {};
local nicks = {};
local usernames = {};
local st = require "util.stanza";
local conference_server = muc_server;
local function irc_close_session(session)
session.conn:close();
end
function irc_listener.onincoming(conn, data)
local session = sessions[conn];
if not session then
session = { conn = conn, host = component_jid, reset_stream = function () end,
close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
rooms = {}, roster = {}, has_un = false };
sessions[conn] = session;
function session.data(data)
local parts = parse_line(data);
module:log("debug", require"util.serialization".serialize(parts));
local command = table.remove(parts, 1);
if not command then
return;
end
command = command:upper();
if not session.username and not session.nick then
if not (command == "USER" or command == "NICK") then
module:log("debug", "Client tried to send command %s before registering", command);
return session.send{from=muc_server, "451", command, "You have not completed the registration."}
end
end
if commands[command] then
local ret = commands[command](session, parts);
if ret then
return session.send(ret);
end
else
session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
return module:log("debug", "Unknown command: %s", command);
end
end
function session.send(data)
if type(data) == "string" then
return conn:write(data.."\r\n");
elseif type(data) == "table" then
local line = build_line(data);
module:log("debug", line);
conn:write(line.."\r\n");
end
end
end
if data then
session.data(data);
end
end
function irc_listener.ondisconnect(conn, error)
local session = sessions[conn];
if session then
for _, room in pairs(session.rooms) do
room:leave("Disconnected");
end
if session.nick then
nicks[session.nick] = nil;
end
if session.full_jid then
jids[session.full_jid] = nil;
end
if session.username then
usernames[session.username] = nil;
end
end
sessions[conn] = nil;
end
local function nick_inuse(nick)
if nicks[nick] then return true else return false end
end
local function check_username(un)
local count = 0;
local result;
for name, given in pairs(usernames) do
if un == given then count = count + 1; end
end
result = count + 1;
if count > 0 then return tostring(un)..tostring(result); else return tostring(un); end
end
local function set_t_data(session, full_jid)
session.full_jid = full_jid;
jids[full_jid] = session;
jids[full_jid]["ar_last"] = {};
jids[full_jid]["nicks_changing"] = {};
if session.nick then nicks[session.nick] = session; end
end
local function send_motd(session)
local nick = session.nick;
if session.username and session.nick then -- send MOTD only if username and nick are set
session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick};
session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version};
session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)}
session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")};
session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "CHANTYPES=# PREFIX=(qaov)~&@+", "are supported by this server"));
session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "STATUSMSG=~&@+", "are supported by this server"));
session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"};
session.send{from = muc_server, "372", nick, "-"};
session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"};
session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."};
session.send{from = muc_server, "372", nick, "-"};
session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."};
session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"};
session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"};
session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."};
session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
end -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
end
function commands.NICK(session, args)
local nick = args[1];
nick = nick:gsub("[^%w_]","");
if session.nick and not nick_inuse(nick) then -- changing nick
local oldnick = session.nick;
-- update and replace session data
session.nick = nick;
nicks[oldnick] = nil;
nicks[nick] = session;
session.send{from=oldnick.."!"..nicks[nick].username, "NICK", nick};
-- broadcast changes if required
if session.rooms then
session.nicks_changing[nick] = { oldnick, session.username };
for id, room in pairs(session.rooms) do room:change_nick(nick); end
session.nicks_changing[nick] = nil;
end
return;
elseif nick_inuse(nick) then
session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return;
end
session.nick = nick;
session.type = "c2s";
nicks[nick] = session;
-- Some choppy clients send in NICK before USER, that needs to be handled
if session.username then
set_t_data(session, jid.join(session.username, component_jid, "ircd"));
end
send_motd(session);
end
function commands.USER(session, params)
local username = params[1];
if not session.has_un then
local un_checked = check_username(username);
usernames[un_checked] = username;
session.username = un_checked;
session.has_un = true;
if not session.full_jid then
set_t_data(session, jid.join(session.username, component_jid, "ircd"));
end
else
return session.send{from=muc_server, "462", "USER", "You may not re-register."}
end
send_motd(session);
end
function commands.USERHOST(session, params) -- can show only users on the gateway. Needed for some clients to determinate self hostmask.
local nick = params[1];
if not nick then session.send{from=muc_server, "461", "USERHOST", "Not enough parameters"}; return; end
if nicks[nick] and nicks[nick].nick and nicks[nick].username then
session.send{from=muc_server, "302", session.nick, nick.."=+"..nicks[nick].username}; return;
else
return;
end
end
local function mode_map(am, rm, nicks)
local rnick;
local c_modes;
c_modes = aff_modemap[am]..role_modemap[rm]
rnick = string.rep(nicks.." ", c_modes:len())
if c_modes == "" then return nil, nil end
return c_modes, rnick
end
function commands.JOIN(session, args)
local channel = args[1];
if not channel then return end
local room_jid = irc2muc(channel);
if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
if not room then
return ":"..muc_server.." ERR :Could not join room: "..err
end
session.rooms[channel] = room;
room.session = session;
if session.nicks_changing[session.nick] then -- my own nick is changing
commands.NAMES(session, channel);
else
session.send{from=session.nick.."!"..session.username, "JOIN", channel};
if room.subject then
session.send{from=muc_server, 332, session.nick, channel, room.subject};
end
commands.NAMES(session, channel);
end
room:hook("subject-changed", function(changed)
session.send{from=changed.by.nick, "TOPIC", channel, changed.to or ""}
end);
room:hook("message", function(event)
if not event.body then return end
local nick, body = event.nick, event.body;
if nick ~= session.nick then
if body:sub(1,4) == "/me " then
body = "\1ACTION ".. body:sub(5) .. "\1"
end
local type = event.stanza.attr.type;
session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body};
--FIXME PM's probably won't work
end
end);
room:hook("presence", function(ar)
local c_modes;
local rnick;
if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end
local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
if x_ar then
local xar_item = x_ar:get_child("item")
if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
if xar_item.attr.affiliation and xar_item.attr.role then
if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
if n_self_changing then return; end
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
else
c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick);
if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
if n_self_changing then return; end
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
end
end
end
end
end, -1);
end
c:hook("groupchat/joined", function(room)
local session = room.session or jids[room.opts.source];
local channel = "#"..room.jid:match("^(.*)@");
room:hook("occupant-joined", function(nick)
if session.nicks_changing[nick.nick] then
session.send{from=session.nicks_changing[nick.nick][1].."!"..(session.nicks_changing[nick.nick][2] or "xmpp"), "NICK", nick.nick};
session.nicks_changing[nick.nick] = nil;
else
session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "JOIN", channel};
end
end);
room:hook("occupant-left", function(nick)
if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end
local status_code =
nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code;
if status_code == "303" then
local newnick =
nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item") and
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item").attr.nick;
session.nicks_changing[newnick] = { nick.nick, (nicks[nick.nick] and nicks[nick.nick].username or "xmpp") }; return;
end
for id, data in pairs(session.nicks_changing) do
if data[1] == nick.nick then return; end
end
session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "PART", channel};
end);
end);
function commands.NAMES(session, channel)
local nicks = { };
if type(channel) == "table" then channel = channel[1] end
local room = session.rooms[channel];
local symbols_map = {
owner = "~",
administrator = "&",
moderator = "@",
member = "+"
}
if not room then return end
-- TODO Break this out into commands.NAMES
for nick, n in pairs(room.occupants) do
if n.affiliation == "owner" and n.role == "moderator" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "administrator" and n.role == "moderator" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "member" and n.role == "moderator" then
nick = symbols_map[n.role]..nick;
elseif n.affiliation == "member" and n.role == "partecipant" then
nick = symbols_map[n.affiliation]..nick;
elseif n.affiliation == "none" and n.role == "moderator" then
nick = symbols_map[n.role]..nick;
end
table.insert(nicks, nick);
end
nicks = table.concat(nicks, " ");
session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks));
session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel));
session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks);
end
function commands.PART(session, args)
local channel, part_message = unpack(args);
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
if not room then return end
channel = channel:match("^([%S]*)");
session.rooms[channel]:leave(part_message);
jids[session.full_jid].ar_last[room.."@"..muc_server] = nil;
session.send{from=session.nick.."!"..session.username, "PART", channel};
end
function commands.PRIVMSG(session, args)
local channel, message = unpack(args);
if message and #message > 0 then
if message:sub(1,8) == "\1ACTION " then
message = "/me ".. message:sub(9,-2)
end
message = utf8_clean(message);
if channel:sub(1,1) == "#" then
if session.rooms[channel] then
module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel);
session.rooms[channel]:send_message(message);
end
else -- private message
local nick = channel;
module:log("debug", "PM to %s", nick);
for channel, room in pairs(session.rooms) do
module:log("debug", "looking for %s in %s", nick, channel);
if room.occupants[nick] then
module:log("debug", "found %s in %s", nick, channel);
local who = room.occupants[nick];
-- FIXME PMs in verse
--room:send_private_message(nick, message);
local pm = st.message({type="chat",to=who.jid}, message);
module:log("debug", "sending PM to %s: %s", nick, tostring(pm));
room:send(pm)
break
end
end
end
end
end
function commands.PING(session, args)
session.send{from=muc_server, "PONG", args[1]};
end
function commands.TOPIC(session, message)
if not message then return end
local channel, topic = message[1], message[2];
channel = utf8_clean(channel);
topic = utf8_clean(topic);
if not channel then return end
local room = session.rooms[channel];
if topic then room:set_subject(topic); end
end
function commands.WHO(session, args)
local channel = args[1];
if session.rooms[channel] then
local room = session.rooms[channel]
for nick in pairs(room.occupants) do
session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
end
session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
end
end
function commands.MODE(session, args) -- Empty command
end
function commands.QUIT(session, args)
session.send{"ERROR", "Closing Link: "..session.nick};
for _, room in pairs(session.rooms) do
room:leave(args[1]);
end
jids[session.full_jid] = nil;
nicks[session.nick] = nil;
usernames[session.username] = nil;
sessions[session.conn] = nil;
session:close();
end
function commands.RAW(session, data) -- Empty command
end
module:provides("net", {
name = "ircd";
listener = irc_listener;
default_port = 7000;
});

8174
mod_ircd.lua Normal file

File diff suppressed because it is too large Load Diff

12
squishy Normal file
View File

@ -0,0 +1,12 @@
Output "mod_ircd.lua"
verse_path = "verse"
Module "verse" (verse_path.."/verse.lua")
--Module "verse" (verse_path.."/init.lua")
--Module "verse.component" (verse_path.."/component.lua")
--Module "verse.plugins.groupchat" (verse_path.."/plugins/groupchat.lua")
--Module "verse.plugins.presence" (verse_path.."/plugins/presence.lua")
Main "mod_ircd.in.lua"

580
verse.orig/verse.lua Normal file
View File

@ -0,0 +1,580 @@
package.preload['verse.plugins.presence'] = (function (...)
function verse.plugins.presence(stream)
stream.last_presence = nil;
stream:hook("presence-out", function (presence)
if not presence.attr.to then
stream.last_presence = presence; -- Cache non-directed presence
end
end, 1);
function stream:resend_presence()
if last_presence then
stream:send(last_presence);
end
end
function stream:set_status(opts)
local p = verse.presence();
if type(opts) == "table" then
if opts.show then
p:tag("show"):text(opts.show):up();
end
if opts.prio then
p:tag("priority"):text(tostring(opts.prio)):up();
end
if opts.msg then
p:tag("status"):text(opts.msg):up();
end
end
-- TODO maybe use opts as prio if it's a int,
-- or as show or status if it's a string?
stream:send(p);
end
end
end)
package.preload['verse.plugins.groupchat'] = (function (...)
local events = require "util.events";
local room_mt = {};
room_mt.__index = room_mt;
local xmlns_delay = "urn:xmpp:delay";
local xmlns_muc = "http://jabber.org/protocol/muc";
function verse.plugins.groupchat(stream)
stream:add_plugin("presence")
stream.rooms = {};
stream:hook("stanza", function (stanza)
local room_jid = jid.bare(stanza.attr.from);
if not room_jid then return end
local room = stream.rooms[room_jid]
if not room and stanza.attr.to and room_jid then
room = stream.rooms[stanza.attr.to.." "..room_jid]
end
if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
if room then
local nick = select(3, jid.split(stanza.attr.from));
local body = stanza:get_child_text("body");
local delay = stanza:get_child("delay", xmlns_delay);
local event = {
room_jid = room_jid;
room = room;
sender = room.occupants[nick];
nick = nick;
body = body;
stanza = stanza;
delay = (delay and delay.attr.stamp);
};
local ret = room:event(stanza.name, event);
return ret or (stanza.name == "message") or nil;
end
end, 500);
function stream:join_room(jid, nick, opts)
if not nick then
return false, "no nickname supplied"
end
opts = opts or {};
local room = setmetatable({
stream = stream, jid = jid, nick = nick,
subject = nil,
occupants = {},
opts = opts,
events = events.new()
}, room_mt);
if opts.source then
self.rooms[opts.source.." "..jid] = room;
else
self.rooms[jid] = room;
end
local occupants = room.occupants;
room:hook("presence", function (presence)
local nick = presence.nick or nick;
if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
occupants[nick] = {
nick = nick;
jid = presence.stanza.attr.from;
presence = presence.stanza;
};
local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
if x then
local x_item = x:get_child("item");
if x_item and x_item.attr then
occupants[nick].real_jid = x_item.attr.jid;
occupants[nick].affiliation = x_item.attr.affiliation;
occupants[nick].role = x_item.attr.role;
end
--TODO Check for status 100?
end
if nick == room.nick then
room.stream:event("groupchat/joined", room);
else
room:event("occupant-joined", occupants[nick]);
end
elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
if nick == room.nick then
room.stream:event("groupchat/left", room);
if room.opts.source then
self.rooms[room.opts.source.." "..jid] = nil;
else
self.rooms[jid] = nil;
end
else
occupants[nick].presence = presence.stanza;
room:event("occupant-left", occupants[nick]);
occupants[nick] = nil;
end
end
end);
room:hook("message", function(event)
local subject = event.stanza:get_child_text("subject");
if not subject then return end
subject = #subject > 0 and subject or nil;
if subject ~= room.subject then
local old_subject = room.subject;
room.subject = subject;
return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
end
end, 2000);
local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
self:event("pre-groupchat/joining", join_st);
room:send(join_st)
self:event("groupchat/joining", room);
return room;
end
stream:hook("presence-out", function(presence)
if not presence.attr.to then
for _, room in pairs(stream.rooms) do
room:send(presence);
end
presence.attr.to = nil;
end
end);
end
function room_mt:send(stanza)
if stanza.name == "message" and not stanza.attr.type then
stanza.attr.type = "groupchat";
end
if stanza.name == "presence" then
stanza.attr.to = self.jid .."/"..self.nick;
end
if stanza.attr.type == "groupchat" or not stanza.attr.to then
stanza.attr.to = self.jid;
end
if self.opts.source then
stanza.attr.from = self.opts.source
end
self.stream:send(stanza);
end
function room_mt:send_message(text)
self:send(verse.message():tag("body"):text(text));
end
function room_mt:set_subject(text)
self:send(verse.message():tag("subject"):text(text));
end
function room_mt:change_nick(new)
self.nick = new;
self:send(verse.presence());
end
function room_mt:leave(message)
self.stream:event("groupchat/leaving", self);
self:send(verse.presence({type="unavailable"}));
end
function room_mt:admin_set(nick, what, value, reason)
self:send(verse.iq({type="set"})
:query(xmlns_muc .. "#admin")
:tag("item", {nick = nick, [what] = value})
:tag("reason"):text(reason or ""));
end
function room_mt:set_role(nick, role, reason)
self:admin_set(nick, "role", role, reason);
end
function room_mt:set_affiliation(nick, affiliation, reason)
self:admin_set(nick, "affiliation", affiliation, reason);
end
function room_mt:kick(nick, reason)
self:set_role(nick, "none", reason);
end
function room_mt:ban(nick, reason)
self:set_affiliation(nick, "outcast", reason);
end
function room_mt:event(name, arg)
self.stream:debug("Firing room event: %s", name);
return self.events.fire_event(name, arg);
end
function room_mt:hook(name, callback, priority)
return self.events.add_handler(name, callback, priority);
end
end)
package.preload['verse.component'] = (function (...)
local verse = require "verse";
local stream = verse.stream_mt;
local jid_split = require "util.jid".split;
local lxp = require "lxp";
local st = require "util.stanza";
local sha1 = require "util.sha1".sha1;
-- Shortcuts to save having to load util.stanza
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
local new_xmpp_stream = require "util.xmppstream".new;
local xmlns_stream = "http://etherx.jabber.org/streams";
local xmlns_component = "jabber:component:accept";
local stream_callbacks = {
stream_ns = xmlns_stream,
stream_tag = "stream",
default_ns = xmlns_component };
function stream_callbacks.streamopened(stream, attr)
stream.stream_id = attr.id;
if not stream:event("opened", attr) then
stream.notopen = nil;
end
return true;
end
function stream_callbacks.streamclosed(stream)
return stream:event("closed");
end
function stream_callbacks.handlestanza(stream, stanza)
if stanza.attr.xmlns == xmlns_stream then
return stream:event("stream-"..stanza.name, stanza);
elseif stanza.attr.xmlns or stanza.name == "handshake" then
return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
end
return stream:event("stanza", stanza);
end
function stream:reset()
if self.stream then
self.stream:reset();
else
self.stream = new_xmpp_stream(self, stream_callbacks);
end
self.notopen = true;
return true;
end
function stream:connect_component(jid, pass)
self.jid, self.password = jid, pass;
self.username, self.host, self.resource = jid_split(jid);
function self.data(conn, data)
local ok, err = self.stream:feed(data);
if ok then return; end
stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
stream:close("xml-not-well-formed");
end
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
self.curr_id = 0;
self.tracked_iqs = {};
self:hook("stanza", function (stanza)
local id, type = stanza.attr.id, stanza.attr.type;
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
self.tracked_iqs[id](stanza);
self.tracked_iqs[id] = nil;
return true;
end
end);
self:hook("stanza", function (stanza)
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if xmlns then
ret = self:event("iq/"..xmlns, stanza);
if not ret then
ret = self:event("iq", stanza);
end
end
if ret == nil then
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
return true;
end
else
ret = self:event(stanza.name, stanza);
end
end
return ret;
end, -1);
self:hook("opened", function (attr)
print(self.jid, self.stream_id, attr.id);
local token = sha1(self.stream_id..pass, true);
self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
self:hook("stream/"..xmlns_component, function (stanza)
if stanza.name == "handshake" then
self:event("authentication-success");
end
end);
end);
local function stream_ready()
self:event("ready");
end
self:hook("authentication-success", stream_ready, -1);
-- Initialise connection
self:connect(self.connect_host or self.host, self.connect_port or 5347);
self:reopen();
end
function stream:reopen()
self:reset();
self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
xmlns = xmlns_component, version = "1.0" }):top_tag());
end
function stream:close(reason)
if not self.notopen then
self:send("</stream:stream>");
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(conn, reason);
end
function stream:send_iq(iq, callback)
local id = self:new_id();
self.tracked_iqs[id] = callback;
iq.attr.id = id;
self:send(iq);
end
function stream:new_id()
self.curr_id = self.curr_id + 1;
return tostring(self.curr_id);
end
end)
-- Use LuaRocks if available
pcall(require, "luarocks.require");
-- Load LuaSec if available
pcall(require, "ssl");
local server = require "net.server";
local events = require "util.events";
local logger = require "util.logger";
module("verse", package.seeall);
local verse = _M;
_M.server = server;
local stream = {};
stream.__index = stream;
stream_mt = stream;
verse.plugins = {};
local max_id = 0;
function verse.new(logger, base)
local t = setmetatable(base or {}, stream);
max_id = max_id + 1;
t.id = tostring(max_id);
t.logger = logger or verse.new_logger("stream"..t.id);
t.events = events.new();
t.plugins = {};
t.verse = verse;
return t;
end
verse.add_task = require "util.timer".add_task;
verse.logger = logger.init; -- COMPAT: Deprecated
verse.new_logger = logger.init;
verse.log = verse.logger("verse");
local function format(format, ...)
local n, arg, maxn = 0, { ... }, select('#', ...);
return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
end
function verse.set_log_handler(log_handler, levels)
levels = levels or { "debug", "info", "warn", "error" };
logger.reset();
local function _log_handler(name, level, message, ...)
return log_handler(name, level, format(message, ...));
end
if log_handler then
for i, level in ipairs(levels) do
logger.add_level_sink(level, _log_handler);
end
end
end
function _default_log_handler(name, level, message)
return io.stderr:write(name, "\t", level, "\t", message, "\n");
end
verse.set_log_handler(_default_log_handler, { "error" });
local function error_handler(err)
verse.log("error", "Error: %s", err);
verse.log("error", "Traceback: %s", debug.traceback());
end
function verse.set_error_handler(new_error_handler)
error_handler = new_error_handler;
end
function verse.loop()
return xpcall(server.loop, error_handler);
end
function verse.step()
return xpcall(server.step, error_handler);
end
function verse.quit()
return server.setquitting(true);
end
function stream:connect(connect_host, connect_port)
connect_host = connect_host or "localhost";
connect_port = tonumber(connect_port) or 5222;
-- Create and initiate connection
local conn = socket.tcp()
conn:settimeout(0);
local success, err = conn:connect(connect_host, connect_port);
if not success and err ~= "timeout" then
self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
return self:event("disconnected", { reason = err }) or false, err;
end
local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
if not conn then
self:warn("connection initialisation failed: %s", err);
return self:event("disconnected", { reason = err }) or false, err;
end
self.conn = conn;
self.send = function (stream, data)
self:event("outgoing", data);
data = tostring(data);
self:event("outgoing-raw", data);
return conn:write(data);
end;
return true;
end
function stream:close()
if not self.conn then
verse.log("error", "Attempt to close disconnected connection - possibly a bug");
return;
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(conn, reason);
end
-- Logging functions
function stream:debug(...)
return self.logger("debug", ...);
end
function stream:warn(...)
return self.logger("warn", ...);
end
function stream:error(...)
return self.logger("error", ...);
end
-- Event handling
function stream:event(name, ...)
self:debug("Firing event: "..tostring(name));
return self.events.fire_event(name, ...);
end
function stream:hook(name, ...)
return self.events.add_handler(name, ...);
end
function stream:unhook(name, handler)
return self.events.remove_handler(name, handler);
end
function verse.eventable(object)
object.events = events.new();
object.hook, object.unhook = stream.hook, stream.unhook;
local fire_event = object.events.fire_event;
function object:event(name, ...)
return fire_event(name, ...);
end
return object;
end
function stream:add_plugin(name)
if self.plugins[name] then return true; end
if require("verse.plugins."..name) then
local ok, err = verse.plugins[name](self);
if ok ~= false then
self:debug("Loaded %s plugin", name);
self.plugins[name] = true;
else
self:warn("Failed to load %s plugin: %s", name, err);
end
end
return self;
end
-- Listener factory
function new_listener(stream)
local conn_listener = {};
function conn_listener.onconnect(conn)
stream.connected = true;
stream:event("connected");
end
function conn_listener.onincoming(conn, data)
stream:event("incoming-raw", data);
end
function conn_listener.ondisconnect(conn, err)
stream.connected = false;
stream:event("disconnected", { reason = err });
end
function conn_listener.ondrain(conn)
stream:event("drained");
end
function conn_listener.onstatus(conn, new_status)
stream:event("status", new_status);
end
return conn_listener;
end
return verse;

BIN
verse/.hg/00changelog.i Normal file

Binary file not shown.

1
verse/.hg/branch Normal file
View File

@ -0,0 +1 @@
default

2
verse/.hg/cache/branchheads-served vendored Normal file
View File

@ -0,0 +1,2 @@
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 368
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 default

BIN
verse/.hg/dirstate Normal file

Binary file not shown.

2
verse/.hg/hgrc Normal file
View File

@ -0,0 +1,2 @@
[paths]
default = http://code.matthewwild.co.uk/verse

4
verse/.hg/requires Normal file
View File

@ -0,0 +1,4 @@
dotencode
fncache
revlogv1
store

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

58
verse/.hg/store/fncache Normal file
View File

@ -0,0 +1,58 @@
data/client.lua.i
data/util/sasl/plain.lua.i
data/plugins/private.lua.i
data/plugins/version.lua.i
data/util/sasl/scram.lua.i
data/plugins/legacy.lua.i
data/plugins/jingle_s5b.lua.i
data/bosh.lua.i
data/doc/example_adhoc.lua.i
data/plugins/roster.lua.i
data/plugins/jingle_ft.lua.i
data/doc/example_bosh.lua.i
data/doc/example_component.lua.i
data/plugins/vcard_update.lua.i
data/plugins/vcard.lua.i
data/plugins/ping.lua.i
data/plugins/session.lua.i
data/plugins/adhoc.lua.i
data/libs/logger.lua.i
data/plugins/uptime.lua.i
data/plugins/pubsub.lua.i
data/libs/xstanza.lua.i
data/doc/example_jingle_send.lua.i
data/plugins/jingle.lua.i
data/plugins/smacks.lua.i
data/doc/example_jingle.lua.i
data/plugins/register.lua.i
data/plugins/carbons.lua.i
data/plugins/jingle_ibb.lua.i
data/init.lua.i
data/plugins/presence.lua.i
data/plugins/blocking.lua.i
data/plugins/pep.lua.i
data/libs/hashes.lua.i
data/squishy.i
data/plugins/bind.lua.i
data/plugins/tls.lua.i
data/doc/example_pep.lua.i
data/util/sha1.lua.i
data/doc/example.lua.i
data/libs/bit.lua.i
data/doc/example_pubsub.lua.i
data/doc/example_carbons.lua.i
data/plugins/receipts.lua.i
data/libs/adhoc.lib.lua.i
data/LICENSE.i
data/plugins/disco.lua.i
data/plugins/archive.lua.i
data/util/sasl/anonymous.lua.i
data/component.lua.i
data/plugins/sasl.lua.i
data/plugins/groupchat.lua.i
data/util/vcard.lua.i
data/plugins/compression.lua.i
data/plugins/proxy65.lua.i
data/util/dataforms.lua.i
data/libs/encodings.lua.i
data/plugins/keepalive.lua.i

View File

BIN
verse/.hg/store/undo Normal file

Binary file not shown.

View File

0
verse/.hg/undo.bookmarks Normal file
View File

1
verse/.hg/undo.branch Normal file
View File

@ -0,0 +1 @@
default

3
verse/.hg/undo.desc Normal file
View File

@ -0,0 +1,3 @@
0
pull
http://code.matthewwild.co.uk/verse

0
verse/.hg/undo.dirstate Normal file
View File

19
verse/LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2009-2010 Matthew Wild
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

209
verse/bosh.lua Normal file
View File

@ -0,0 +1,209 @@
local new_xmpp_stream = require "util.xmppstream".new;
local st = require "util.stanza";
require "net.httpclient_listener"; -- Required for net.http to work
local http = require "net.http";
local stream_mt = setmetatable({}, { __index = verse.stream_mt });
stream_mt.__index = stream_mt;
local xmlns_stream = "http://etherx.jabber.org/streams";
local xmlns_bosh = "http://jabber.org/protocol/httpbind";
local reconnect_timeout = 5;
function verse.new_bosh(logger, url)
local stream = {
bosh_conn_pool = {};
bosh_waiting_requests = {};
bosh_rid = math.random(1,999999);
bosh_outgoing_buffer = {};
bosh_url = url;
conn = {};
};
function stream:reopen()
self.bosh_need_restart = true;
self:flush();
end
local conn = verse.new(logger, stream);
return setmetatable(conn, stream_mt);
end
function stream_mt:connect()
self:_send_session_request();
end
function stream_mt:send(data)
self:debug("Putting into BOSH send buffer: %s", tostring(data));
self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1] = st.clone(data);
self:flush(); --TODO: Optimize by doing this on next tick (give a chance for data to buffer)
end
function stream_mt:flush()
if self.connected
and #self.bosh_waiting_requests < self.bosh_max_requests
and (#self.bosh_waiting_requests == 0
or #self.bosh_outgoing_buffer > 0
or self.bosh_need_restart) then
self:debug("Flushing...");
local payload = self:_make_body();
local buffer = self.bosh_outgoing_buffer;
for i, stanza in ipairs(buffer) do
payload:add_child(stanza);
buffer[i] = nil;
end
self:_make_request(payload);
else
self:debug("Decided not to flush.");
end
end
function stream_mt:_make_request(payload)
local request, err = http.request(self.bosh_url, { body = tostring(payload) }, function (response, code, request)
if code ~= 0 then
self.inactive_since = nil;
return self:_handle_response(response, code, request);
end
-- Connection issues, we need to retry this request
local time = os.time();
if not self.inactive_since then
self.inactive_since = time; -- So we know when it is time to give up
elseif time - self.inactive_since > self.bosh_max_inactivity then
return self:_disconnected();
else
self:debug("%d seconds left to reconnect, retrying in %d seconds...",
self.bosh_max_inactivity - (time - self.inactive_since), reconnect_timeout);
end
-- Set up reconnect timer
timer.add_task(reconnect_timeout, function ()
self:debug("Retrying request...");
-- Remove old request
for i, waiting_request in ipairs(self.bosh_waiting_requests) do
if waiting_request == request then
table.remove(self.bosh_waiting_requests, i);
break;
end
end
self:_make_request(payload);
end);
end);
if request then
table.insert(self.bosh_waiting_requests, request);
else
self:warn("Request failed instantly: %s", err);
end
end
function stream_mt:_disconnected()
self.connected = nil;
self:event("disconnected");
end
function stream_mt:_send_session_request()
local body = self:_make_body();
-- XEP-0124
body.attr.hold = "1";
body.attr.wait = "60";
body.attr["xml:lang"] = "en";
body.attr.ver = "1.6";
-- XEP-0206
body.attr.from = self.jid;
body.attr.to = self.host;
body.attr.secure = 'true';
http.request(self.bosh_url, { body = tostring(body) }, function (response, code)
if code == 0 then
-- Failed to connect
return self:_disconnected();
end
-- Handle session creation response
local payload = self:_parse_response(response)
if not payload then
self:warn("Invalid session creation response");
self:_disconnected();
return;
end
self.bosh_sid = payload.attr.sid; -- Session id
self.bosh_wait = tonumber(payload.attr.wait); -- How long the server may hold connections for
self.bosh_hold = tonumber(payload.attr.hold); -- How many connections the server may hold
self.bosh_max_inactivity = tonumber(payload.attr.inactivity); -- Max amount of time with no connections
self.bosh_max_requests = tonumber(payload.attr.requests) or self.bosh_hold; -- Max simultaneous requests we can make
self.connected = true;
self:event("connected");
self:_handle_response_payload(payload);
end);
end
function stream_mt:_handle_response(response, code, request)
if self.bosh_waiting_requests[1] ~= request then
self:warn("Server replied to request that wasn't the oldest");
for i, waiting_request in ipairs(self.bosh_waiting_requests) do
if waiting_request == request then
self.bosh_waiting_requests[i] = nil;
break;
end
end
else
table.remove(self.bosh_waiting_requests, 1);
end
local payload = self:_parse_response(response);
if payload then
self:_handle_response_payload(payload);
end
self:flush();
end
function stream_mt:_handle_response_payload(payload)
local stanzas = payload.tags;
for i = 1, #stanzas do
local stanza = stanzas[i];
if stanza.attr.xmlns == xmlns_stream then
self:event("stream-"..stanza.name, stanza);
elseif stanza.attr.xmlns then
self:event("stream/"..stanza.attr.xmlns, stanza);
else
self:event("stanza", stanza);
end
end
if payload.attr.type == "terminate" then
self:_disconnected({reason = payload.attr.condition});
end
end
local stream_callbacks = {
stream_ns = "http://jabber.org/protocol/httpbind", stream_tag = "body",
default_ns = "jabber:client",
streamopened = function (session, attr) session.notopen = nil; session.payload = verse.stanza("body", attr); return true; end;
handlestanza = function (session, stanza) session.payload:add_child(stanza); end;
};
function stream_mt:_parse_response(response)
self:debug("Parsing response: %s", response);
if response == nil then
self:debug("%s", debug.traceback());
self:_disconnected();
return;
end
local session = { notopen = true, stream = self };
local stream = new_xmpp_stream(session, stream_callbacks);
stream:feed(response);
return session.payload;
end
function stream_mt:_make_body()
self.bosh_rid = self.bosh_rid + 1;
local body = verse.stanza("body", {
xmlns = xmlns_bosh;
content = "text/xml; charset=utf-8";
sid = self.bosh_sid;
rid = self.bosh_rid;
});
if self.bosh_need_restart then
self.bosh_need_restart = nil;
body.attr.restart = 'true';
end
return body;
end

219
verse/client.lua Normal file
View File

@ -0,0 +1,219 @@
local verse = require "verse";
local stream = verse.stream_mt;
local jid_split = require "util.jid".split;
local adns = require "net.adns";
local lxp = require "lxp";
local st = require "util.stanza";
-- Shortcuts to save having to load util.stanza
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
local new_xmpp_stream = require "util.xmppstream".new;
local xmlns_stream = "http://etherx.jabber.org/streams";
local function compare_srv_priorities(a,b)
return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
end
local stream_callbacks = {
stream_ns = xmlns_stream,
stream_tag = "stream",
default_ns = "jabber:client" };
function stream_callbacks.streamopened(stream, attr)
stream.stream_id = attr.id;
if not stream:event("opened", attr) then
stream.notopen = nil;
end
return true;
end
function stream_callbacks.streamclosed(stream)
stream.notopen = true;
if not stream.closed then
stream:send("</stream:stream>");
stream.closed = true;
end
stream:event("closed");
return stream:close("stream closed")
end
function stream_callbacks.handlestanza(stream, stanza)
if stanza.attr.xmlns == xmlns_stream then
return stream:event("stream-"..stanza.name, stanza);
elseif stanza.attr.xmlns then
return stream:event("stream/"..stanza.attr.xmlns, stanza);
end
return stream:event("stanza", stanza);
end
function stream_callbacks.error(stream, e, stanza)
if stream:event(e, stanza) == nil then
if stanza then
local err = stanza:get_child(nil, "urn:ietf:params:xml:ns:xmpp-streams");
local text = stanza:get_child_text("text", "urn:ietf:params:xml:ns:xmpp-streams");
error(err.name..(text and ": "..text or ""));
else
error(stanza and stanza.name or e or "unknown-error");
end
end
end
function stream:reset()
if self.stream then
self.stream:reset();
else
self.stream = new_xmpp_stream(self, stream_callbacks);
end
self.notopen = true;
return true;
end
function stream:connect_client(jid, pass)
self.jid, self.password = jid, pass;
self.username, self.host, self.resource = jid_split(jid);
-- Required XMPP features
self:add_plugin("tls");
self:add_plugin("sasl");
self:add_plugin("bind");
self:add_plugin("session");
function self.data(conn, data)
local ok, err = self.stream:feed(data);
if ok then return; end
self:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
self:close("xml-not-well-formed");
end
self:hook("connected", function () self:reopen(); end);
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
self.curr_id = 0;
self.tracked_iqs = {};
self:hook("stanza", function (stanza)
local id, type = stanza.attr.id, stanza.attr.type;
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
self.tracked_iqs[id](stanza);
self.tracked_iqs[id] = nil;
return true;
end
end);
self:hook("stanza", function (stanza)
local ret;
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if xmlns then
ret = self:event("iq/"..xmlns, stanza);
if not ret then
ret = self:event("iq", stanza);
end
end
if ret == nil then
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
return true;
end
else
ret = self:event(stanza.name, stanza);
end
end
return ret;
end, -1);
self:hook("outgoing", function (data)
if data.name then
self:event("stanza-out", data);
end
end);
self:hook("stanza-out", function (stanza)
if not stanza.attr.xmlns then
self:event(stanza.name.."-out", stanza);
end
end);
local function stream_ready()
self:event("ready");
end
self:hook("session-success", stream_ready, -1)
self:hook("bind-success", stream_ready, -1);
local _base_close = self.close;
function self:close(reason)
self.close = _base_close;
if not self.closed then
self:send("</stream:stream>");
self.closed = true;
else
return self:close(reason);
end
end
local function start_connect()
-- Initialise connection
self:connect(self.connect_host or self.host, self.connect_port or 5222);
end
if not (self.connect_host or self.connect_port) then
-- Look up SRV records
adns.lookup(function (answer)
if answer then
local srv_hosts = {};
self.srv_hosts = srv_hosts;
for _, record in ipairs(answer) do
table.insert(srv_hosts, record.srv);
end
table.sort(srv_hosts, compare_srv_priorities);
local srv_choice = srv_hosts[1];
self.srv_choice = 1;
if srv_choice then
self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
self:debug("Best record found, will connect to %s:%d", self.connect_host or self.host, self.connect_port or 5222);
end
self:hook("disconnected", function ()
if self.srv_hosts and self.srv_choice < #self.srv_hosts then
self.srv_choice = self.srv_choice + 1;
local srv_choice = srv_hosts[self.srv_choice];
self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
start_connect();
return true;
end
end, 1000);
self:hook("connected", function ()
self.srv_hosts = nil;
end, 1000);
end
start_connect();
end, "_xmpp-client._tcp."..(self.host)..".", "SRV");
else
start_connect();
end
end
function stream:reopen()
self:reset();
self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
xmlns = "jabber:client", version = "1.0" }):top_tag());
end
function stream:send_iq(iq, callback)
local id = self:new_id();
self.tracked_iqs[id] = callback;
iq.attr.id = id;
self:send(iq);
end
function stream:new_id()
self.curr_id = self.curr_id + 1;
return tostring(self.curr_id);
end

149
verse/component.lua Normal file
View File

@ -0,0 +1,149 @@
local verse = require "verse";
local stream = verse.stream_mt;
local jid_split = require "util.jid".split;
local lxp = require "lxp";
local st = require "util.stanza";
local sha1 = require "util.sha1".sha1;
-- Shortcuts to save having to load util.stanza
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
local new_xmpp_stream = require "util.xmppstream".new;
local xmlns_stream = "http://etherx.jabber.org/streams";
local xmlns_component = "jabber:component:accept";
local stream_callbacks = {
stream_ns = xmlns_stream,
stream_tag = "stream",
default_ns = xmlns_component };
function stream_callbacks.streamopened(stream, attr)
stream.stream_id = attr.id;
if not stream:event("opened", attr) then
stream.notopen = nil;
end
return true;
end
function stream_callbacks.streamclosed(stream)
return stream:event("closed");
end
function stream_callbacks.handlestanza(stream, stanza)
if stanza.attr.xmlns == xmlns_stream then
return stream:event("stream-"..stanza.name, stanza);
elseif stanza.attr.xmlns or stanza.name == "handshake" then
return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
end
return stream:event("stanza", stanza);
end
function stream:reset()
if self.stream then
self.stream:reset();
else
self.stream = new_xmpp_stream(self, stream_callbacks);
end
self.notopen = true;
return true;
end
function stream:connect_component(jid, pass)
self.jid, self.password = jid, pass;
self.username, self.host, self.resource = jid_split(jid);
function self.data(conn, data)
local ok, err = self.stream:feed(data);
if ok then return; end
stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
stream:close("xml-not-well-formed");
end
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
self.curr_id = 0;
self.tracked_iqs = {};
self:hook("stanza", function (stanza)
local id, type = stanza.attr.id, stanza.attr.type;
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
self.tracked_iqs[id](stanza);
self.tracked_iqs[id] = nil;
return true;
end
end);
self:hook("stanza", function (stanza)
local ret;
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
if xmlns then
ret = self:event("iq/"..xmlns, stanza);
if not ret then
ret = self:event("iq", stanza);
end
end
if ret == nil then
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
return true;
end
else
ret = self:event(stanza.name, stanza);
end
end
return ret;
end, -1);
self:hook("opened", function (attr)
print(self.jid, self.stream_id, attr.id);
local token = sha1(self.stream_id..pass, true);
self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
self:hook("stream/"..xmlns_component, function (stanza)
if stanza.name == "handshake" then
self:event("authentication-success");
end
end);
end);
local function stream_ready()
self:event("ready");
end
self:hook("authentication-success", stream_ready, -1);
-- Initialise connection
self:connect(self.connect_host or self.host, self.connect_port or 5347);
self:reopen();
end
function stream:reopen()
self:reset();
self:send(st.stanza("stream:stream", { to = self.jid, ["xmlns:stream"]='http://etherx.jabber.org/streams',
xmlns = xmlns_component, version = "1.0" }):top_tag());
end
function stream:close(reason)
if not self.notopen then
self:send("</stream:stream>");
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(conn, reason);
end
function stream:send_iq(iq, callback)
local id = self:new_id();
self.tracked_iqs[id] = callback;
iq.attr.id = id;
self:send(iq);
end
function stream:new_id()
self.curr_id = self.curr_id + 1;
return tostring(self.curr_id);
end

39
verse/doc/example.lua Normal file
View File

@ -0,0 +1,39 @@
-- Change these:
local jid, password = "user@example.com", "secret";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none verse");
require "verse".init("client");
c = verse.new();
c:add_plugin("version");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
c.version:set{ name = "verse example client" };
c:query_version(c.jid, function (v) print("I am using "..(v.name or "<unknown>")); end);
end);
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,48 @@
-- Change these:
local jid, password = "user@example.com", "secret";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none");
require "verse".init("client");
c = verse.new(verse.logger());
c:add_plugin("version");
c:add_plugin("disco");
c:add_plugin("adhoc");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
c.version:set{ name = "verse example client" };
local function random_handler()
return { info = tostring(math.random(1,100)), status = "completed" };
end
c:add_adhoc_command("Get random number", "random", random_handler);
c:send(verse.presence():add_child(c:caps()));
end);
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,40 @@
-- Change these:
local jid, password = "user@example.com", "secret";
local url = "http://example.com:80/http-bind";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none verse");
require "verse".init("client", "bosh");
c = verse.new_bosh(nil, url);
c:add_plugin("version");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
c.version:set{ name = "Verse example BOSH client" };
c:query_version(c.jid, function (v) print("I am using "..(v.name or "<unknown>")); end);
end);
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,46 @@
local xmlns_carbons = "urn:xmpp:carbons:1";
local xmlns_forward = "urn:xmpp:forward:0";
local function datetime(t) return os_date("!%Y-%m-%dT%H:%M:%SZ", t); end
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none verse");
require "verse".init("client");
c = verse.new();--verse.logger());
c:add_plugin "carbons"
c:hook("disconnected", verse.quit);
local jid, password = unpack(arg);
assert(jid and password, "You need to supply JID and password as arguments");
c:connect_client(jid, password);
-- Print a message after authentication
c:hook("authentication-success", function () c:debug("Logged in!"); end);
c:hook("authentication-failure", function (err)
c:error("Failed to log in! Error: "..tostring(err.condition));
c:close();
end);
c:hook("carbon", function(carbon)
local dir, ts, st = carbon.dir, carbon.timestamp, carbon.stanza;
print("", datetime(ts), dir:upper());
print(st);
end);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
c:debug("Connected");
c.carbons:enable(function(ok)
if ok then
c:debug("Carbons enabled")
else
c:error("Could not enable carbons, aborting");
c:close();
end
end);
end);
verse.loop()

View File

@ -0,0 +1,43 @@
local jid, password = "echo.localhost", "hellohello";
--os.execute("squish --minify-level=none verse");
require "verse".init("component");
c = verse.new(verse.logger())
c:add_plugin("version");
c:add_plugin("ping");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c.connect_host = "127.0.0.1"
c:connect_component(jid, password);
-- Catch binding-success which is (currently) how you know when a stream is ready
c:hook("ready", function ()
print("Stream ready!");
c.version:set{ name = "verse example component" };
end);
-- Echo, echo, echo, echo...
c:hook("stanza", function (stanza)
stanza.attr.from, stanza.attr.to = stanza.attr.to, stanza.attr.from;
c:send(stanza);
end)
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,56 @@
-- Change these:
local jid, password = "user@example.com/receiver", "secret";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none");
require "verse".init("client");
c = verse.new(verse.logger())
c:add_plugin("version");
c:add_plugin("disco");
c:add_plugin("proxy65");
c:add_plugin("jingle");
c:add_plugin("jingle_ft");
c:add_plugin("jingle_s5b");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end, 15);
-- This one prints all received data
--c:hook("incoming-raw", function (...) print("<<", ...) end, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
-- Handle incoming Jingle requests
c:hook("jingle", function (jingle)
if jingle.content.type == "file" then
print("File offer from "..jingle.peer.."!");
print("Filename: "..jingle.content.file.name.." Size: "..jingle.content.file.size);
jingle:accept({ save_file = jingle.content.file.name..".received" });
end
end);
c:send(verse.presence()
:tag("status"):text("Send me a file!"):up()
:add_child(c:caps())
);
end);
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,52 @@
-- Change these:
local jid, password = "user@example.com/sender", "secret";
local receiver = "user@example.com/receiver";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none");
require "verse".init("client");
c = verse.new(verse.logger())
c:add_plugin("version");
c:add_plugin("disco");
c:add_plugin("proxy65");
c:add_plugin("jingle");
c:add_plugin("jingle_ft");
c:add_plugin("jingle_s5b");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end, 15);
-- This one prints all received data
--c:hook("incoming-raw", function (...) print("<<", ...) end, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
c:send(verse.presence()
:tag("status"):text("Let me send you a file!"):up()
:add_child(c:caps())
);
c:hook("proxy65/discovered-proxies", function ()
print(c:send_file(receiver, "jingle.txt"));
end);
end);
print("Starting loop...")
verse.loop()

56
verse/doc/example_pep.lua Normal file
View File

@ -0,0 +1,56 @@
-- Change these:
local jid, password = "user@example.com", "secret";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none");
require "verse".init("client");
c = verse.new();
c:add_plugin("version");
c:add_plugin("pep");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
c.version:set{ name = "verse example client" };
c:hook_pep("http://jabber.org/protocol/mood", function (event)
print(event.from.." is "..event.item.tags[1].name);
end);
c:hook_pep("http://jabber.org/protocol/tune", function (event)
print(event.from.." is listening to "..event.item:get_child_text("title"));
end);
c:send(verse.presence());
c:publish_pep(verse.stanza("tune", { xmlns = "http://jabber.org/protocol/tune" })
:tag("title"):text("Beautiful Cedars"):up()
:tag("artist"):text("The Spinners"):up()
:tag("source"):text("Not Quite Folk"):up()
:tag("track"):text("4"):up()
);
end);
print("Starting loop...")
verse.loop()

View File

@ -0,0 +1,56 @@
-- Change these:
local jid, password = "user@example.com", "secret";
-- This line squishes verse each time you run,
-- handy if you're hacking on Verse itself
--os.execute("squish --minify-level=none");
require "verse".init("client");
c = verse.new();
c:add_plugin("pubsub");
-- Add some hooks for debugging
c:hook("opened", function () print("Stream opened!") end);
c:hook("closed", function () print("Stream closed!") end);
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
-- This one prints all received data
c:hook("incoming-raw", print, 1000);
-- Print a message after authentication
c:hook("authentication-success", function () print("Logged in!"); end);
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
-- Print a message and exit when disconnected
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
-- Now, actually start the connection:
c:connect_client(jid, password);
-- Catch the "ready" event to know when the stream is ready to use
c:hook("ready", function ()
print("Stream ready!");
-- Create a reference to a node
local node = c.pubsub("pubsub.shakespeare.lit", "princely_musings");
-- Callback for when something is published to the node
node:hook(function(event)
print(event.item)
end);
node:subscribe() -- so we actually get the notifications that above callback would get
node:publish(
nil, -- no id, so the service should give us one
nil, -- no options (not supported at the time of this writing)
verse.stanza("something", { xmlns = "http://example.com/pubsub-thingy" }) -- the actual payload, would turn up in event.item above
:tag("foobar"),
function(success) -- callback
print("publish", success and "success" or "failure")
end)
end);
print("Starting loop...")
verse.loop()

250
verse/init.lua Normal file
View File

@ -0,0 +1,250 @@
-- Use LuaRocks if available
pcall(require, "luarocks.require");
local socket = require"socket";
-- Load LuaSec if available
pcall(require, "ssl");
local server = require "net.server";
local events = require "util.events";
local logger = require "util.logger";
module("verse", package.seeall);
local verse = _M;
_M.server = server;
local stream = {};
stream.__index = stream;
stream_mt = stream;
verse.plugins = {};
function verse.init(...)
for i=1,select("#", ...) do
local ok = pcall(require, "verse."..select(i,...));
if not ok then
error("Verse connection module not found: verse."..select(i,...));
end
end
return verse;
end
local max_id = 0;
function verse.new(logger, base)
local t = setmetatable(base or {}, stream);
max_id = max_id + 1;
t.id = tostring(max_id);
t.logger = logger or verse.new_logger("stream"..t.id);
t.events = events.new();
t.plugins = {};
t.verse = verse;
return t;
end
verse.add_task = require "util.timer".add_task;
verse.logger = logger.init; -- COMPAT: Deprecated
verse.new_logger = logger.init;
verse.log = verse.logger("verse");
local function format(format, ...)
local n, arg, maxn = 0, { ... }, select('#', ...);
return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
end
function verse.set_log_handler(log_handler, levels)
levels = levels or { "debug", "info", "warn", "error" };
logger.reset();
if io.type(log_handler) == "file" then
local f = log_handler;
function log_handler(name, level, message)
f:write(name, "\t", level, "\t", message, "\n");
end
end
if log_handler then
local function _log_handler(name, level, message, ...)
return log_handler(name, level, format(message, ...));
end
for i, level in ipairs(levels) do
logger.add_level_sink(level, _log_handler);
end
end
end
function _default_log_handler(name, level, message)
return io.stderr:write(name, "\t", level, "\t", message, "\n");
end
verse.set_log_handler(_default_log_handler, { "error" });
local function error_handler(err)
verse.log("error", "Error: %s", err);
verse.log("error", "Traceback: %s", debug.traceback());
end
function verse.set_error_handler(new_error_handler)
error_handler = new_error_handler;
end
function verse.loop()
return xpcall(server.loop, error_handler);
end
function verse.step()
return xpcall(server.step, error_handler);
end
function verse.quit()
return server.setquitting(true);
end
function stream:listen(host, port)
host = host or "localhost";
port = port or 0;
local conn, err = server.addserver(host, port, new_listener(self, "server"), "*a");
if conn then
self:debug("Bound to %s:%s", host, port);
self.server = conn;
end
return conn, err;
end
function stream:connect(connect_host, connect_port)
connect_host = connect_host or "localhost";
connect_port = tonumber(connect_port) or 5222;
-- Create and initiate connection
local conn = socket.tcp()
conn:settimeout(0);
local success, err = conn:connect(connect_host, connect_port);
if not success and err ~= "timeout" then
self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
return self:event("disconnected", { reason = err }) or false, err;
end
local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
if not conn then
self:warn("connection initialisation failed: %s", err);
return self:event("disconnected", { reason = err }) or false, err;
end
self:set_conn(conn);
return true;
end
function stream:set_conn(conn)
self.conn = conn;
self.send = function (stream, data)
self:event("outgoing", data);
data = tostring(data);
self:event("outgoing-raw", data);
return conn:write(data);
end;
end
function stream:close(reason)
if not self.conn then
verse.log("error", "Attempt to close disconnected connection - possibly a bug");
return;
end
local on_disconnect = self.conn.disconnect();
self.conn:close();
on_disconnect(self.conn, reason);
end
-- Logging functions
function stream:debug(...)
return self.logger("debug", ...);
end
function stream:info(...)
return self.logger("info", ...);
end
function stream:warn(...)
return self.logger("warn", ...);
end
function stream:error(...)
return self.logger("error", ...);
end
-- Event handling
function stream:event(name, ...)
self:debug("Firing event: "..tostring(name));
return self.events.fire_event(name, ...);
end
function stream:hook(name, ...)
return self.events.add_handler(name, ...);
end
function stream:unhook(name, handler)
return self.events.remove_handler(name, handler);
end
function verse.eventable(object)
object.events = events.new();
object.hook, object.unhook = stream.hook, stream.unhook;
local fire_event = object.events.fire_event;
function object:event(name, ...)
return fire_event(name, ...);
end
return object;
end
function stream:add_plugin(name)
if self.plugins[name] then return true; end
if require("verse.plugins."..name) then
local ok, err = verse.plugins[name](self);
if ok ~= false then
self:debug("Loaded %s plugin", name);
self.plugins[name] = true;
else
self:warn("Failed to load %s plugin: %s", name, err);
end
end
return self;
end
-- Listener factory
function new_listener(stream)
local conn_listener = {};
function conn_listener.onconnect(conn)
if stream.server then
local client = verse.new();
conn:setlistener(new_listener(client));
client:set_conn(conn);
stream:event("connected", { client = client });
else
stream.connected = true;
stream:event("connected");
end
end
function conn_listener.onincoming(conn, data)
stream:event("incoming-raw", data);
end
function conn_listener.ondisconnect(conn, err)
if conn ~= stream.conn then return end
stream.connected = false;
stream:event("disconnected", { reason = err });
end
function conn_listener.ondrain(conn)
stream:event("drained");
end
function conn_listener.onstatus(conn, new_status)
stream:event("status", new_status);
end
return conn_listener;
end
return verse;

85
verse/libs/adhoc.lib.lua Normal file
View File

@ -0,0 +1,85 @@
-- Copyright (C) 2009-2010 Florian Zeitz
--
-- This file is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st, uuid = require "util.stanza", require "util.uuid";
local xmlns_cmd = "http://jabber.org/protocol/commands";
local states = {}
local _M = {};
function _cmdtag(desc, status, sessionid, action)
local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status });
if sessionid then cmd.attr.sessionid = sessionid; end
if action then cmd.attr.action = action; end
return cmd;
end
function _M.new(name, node, handler, permission)
return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") };
end
function _M.handle_cmd(command, origin, stanza)
local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
local dataIn = {};
dataIn.to = stanza.attr.to;
dataIn.from = stanza.attr.from;
dataIn.action = stanza.tags[1].attr.action or "execute";
dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data");
local data, state = command:handler(dataIn, states[sessionid]);
states[sessionid] = state;
local stanza = st.reply(stanza);
if data.status == "completed" then
states[sessionid] = nil;
cmdtag = command:cmdtag("completed", sessionid);
elseif data.status == "canceled" then
states[sessionid] = nil;
cmdtag = command:cmdtag("canceled", sessionid);
elseif data.status == "error" then
states[sessionid] = nil;
stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message);
origin.send(stanza);
return true;
else
cmdtag = command:cmdtag("executing", sessionid);
end
for name, content in pairs(data) do
if name == "info" then
cmdtag:tag("note", {type="info"}):text(content):up();
elseif name == "warn" then
cmdtag:tag("note", {type="warn"}):text(content):up();
elseif name == "error" then
cmdtag:tag("note", {type="error"}):text(content.message):up();
elseif name =="actions" then
local actions = st.stanza("actions");
for _, action in ipairs(content) do
if (action == "prev") or (action == "next") or (action == "complete") then
actions:tag(action):up();
else
module:log("error", 'Command "'..command.name..
'" at node "'..command.node..'" provided an invalid action "'..action..'"');
end
end
cmdtag:add_child(actions);
elseif name == "form" then
cmdtag:add_child((content.layout or content):form(content.values));
elseif name == "result" then
cmdtag:add_child((content.layout or content):form(content.values, "result"));
elseif name == "other" then
cmdtag:add_child(content);
end
end
stanza:add_child(cmdtag);
origin.send(stanza);
return true;
end
return _M;

145
verse/libs/bit.lua Normal file
View File

@ -0,0 +1,145 @@
-- 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 type = type;
local tonumber = tonumber;
local setmetatable = setmetatable;
local error = error;
local tostring = tostring;
local print = print;
local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;};
local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;};
local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;}
local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;};
local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;};
local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;};
local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;};
local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;};
local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;};
module "bit"
local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end};
local function do_bop(a, b, op)
return setmetatable({
op[a[1]*16+b[1]];
op[a[2]*16+b[2]];
op[a[3]*16+b[3]];
op[a[4]*16+b[4]];
op[a[5]*16+b[5]];
op[a[6]*16+b[6]];
op[a[7]*16+b[7]];
op[a[8]*16+b[8]];
}, bit_mt);
end
local function do_uop(a, op)
return setmetatable({
op[a[1]];
op[a[2]];
op[a[3]];
op[a[4]];
op[a[5]];
op[a[6]];
op[a[7]];
op[a[8]];
}, bit_mt);
end
function bxor(a, b) return do_bop(a, b, xor_map); end
function bor(a, b) return do_bop(a, b, or_map); end
function band(a, b) return do_bop(a, b, and_map); end
function bnot(a) return do_uop(a, not_map); end
local function _rshift1(t)
local carry = 0;
for i=1,8 do
local t_i = rshift1_map[t[i]] + carry;
carry = rshift1carry_map[t[i]];
t[i] = t_i;
end
end
function rshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
for n = 1,i do _rshift1(t); end
return setmetatable(t, bit_mt);
end
local function _arshift1(t)
local carry = arshift1carry_map[t[1]];
for i=1,8 do
local t_i = rshift1_map[t[i]] + carry;
carry = rshift1carry_map[t[i]];
t[i] = t_i;
end
end
function arshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
for n = 1,i do _arshift1(t); end
return setmetatable(t, bit_mt);
end
local function _lshift1(t)
local carry = 0;
for i=8,1,-1 do
local t_i = lshift1_map[t[i]] + carry;
carry = lshift1carry_map[t[i]];
t[i] = t_i;
end
end
function lshift(a, i)
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
for n = 1,i do _lshift1(t); end
return setmetatable(t, bit_mt);
end
local function _cast(a)
if type(a) == "number" then a = ("%x"):format(a);
elseif type(a) == "table" then return a;
elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end
local t = {0,0,0,0,0,0,0,0};
a = "00000000"..a;
a = a:sub(-8);
for i = 1,8 do
t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2);
end
return setmetatable(t, bit_mt);
end
local function wrap1(f)
return function(a, ...)
if type(a) ~= "table" then a = _cast(a); end
a = f(a, ...);
a = tonumber(tostring(a), 16);
if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
return a;
end;
end
local function wrap2(f)
return function(a, b, ...)
if type(a) ~= "table" then a = _cast(a); end
if type(b) ~= "table" then b = _cast(b); end
a = f(a, b, ...);
a = tonumber(tostring(a), 16);
if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
return a;
end;
end
bxor = wrap2(bxor);
bor = wrap2(bor);
band = wrap2(band);
bnot = wrap1(bnot);
lshift = wrap1(lshift);
rshift = wrap1(rshift);
arshift = wrap1(arshift);
cast = wrap1(_cast);
bits = 32;
return _M;

12
verse/libs/encodings.lua Normal file
View File

@ -0,0 +1,12 @@
local function not_impl()
error("Function not implemented");
end
local mime = require "mime";
module "encodings"
stringprep = {};
base64 = { encode = mime.b64, decode = not_impl }; --mime.unb64 is buggy with \0
return _M;

3
verse/libs/hashes.lua Normal file
View File

@ -0,0 +1,3 @@
local sha1 = require "util.sha1";
return { sha1 = sha1.sha1 };

27
verse/libs/xstanza.lua Normal file
View File

@ -0,0 +1,27 @@
local stanza_mt = getmetatable(require "util.stanza".stanza());
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
function stanza_mt:get_error()
local type, condition, text;
local error_tag = self:get_child("error");
if not error_tag then
return nil, nil;
end
type = error_tag.attr.type;
for child in error_tag:children() do
if child.attr.xmlns == xmlns_stanzas then
if child.name == "text" then
text = child:get_text();
else
condition = child.name;
end
if condition and text then
break;
end
end
end
return type, condition, text;
end

115
verse/plugins/adhoc.lua Normal file
View File

@ -0,0 +1,115 @@
local verse = require "verse";
local adhoc = require "lib.adhoc";
local xmlns_commands = "http://jabber.org/protocol/commands";
local xmlns_data = "jabber:x:data";
local command_mt = {};
command_mt.__index = command_mt;
-- Table of commands we provide
local commands = {};
function verse.plugins.adhoc(stream)
stream:add_plugin("disco");
stream:add_disco_feature(xmlns_commands);
function stream:query_commands(jid, callback)
stream:disco_items(jid, xmlns_commands, function (items)
stream:debug("adhoc list returned")
local command_list = {};
for _, item in ipairs(items) do
command_list[item.node] = item.name;
end
stream:debug("adhoc calling callback")
return callback(command_list);
end);
end
function stream:execute_command(jid, command, callback)
local cmd = setmetatable({
stream = stream, jid = jid,
command = command, callback = callback
}, command_mt);
return cmd:execute();
end
-- ACL checker for commands we provide
local function has_affiliation(jid, aff)
if not(aff) or aff == "user" then return true; end
if type(aff) == "function" then
return aff(jid);
end
-- TODO: Support 'roster', etc.
end
function stream:add_adhoc_command(name, node, handler, permission)
commands[node] = adhoc.new(name, node, handler, permission);
stream:add_disco_item({ jid = stream.jid, node = node, name = name }, xmlns_commands);
return commands[node];
end
local function handle_command(stanza)
local command_tag = stanza.tags[1];
local node = command_tag.attr.node;
local handler = commands[node];
if not handler then return; end
if not has_affiliation(stanza.attr.from, handler.permission) then
stream:send(verse.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
:add_child(handler:cmdtag("canceled")
:tag("note", {type="error"}):text("You don't have permission to execute this command")));
return true
end
-- User has permission now execute the command
return adhoc.handle_cmd(handler, { send = function (d) return stream:send(d) end }, stanza);
end
stream:hook("iq/"..xmlns_commands, function (stanza)
local type = stanza.attr.type;
local name = stanza.tags[1].name;
if type == "set" and name == "command" then
return handle_command(stanza);
end
end);
end
function command_mt:_process_response(result)
if result.attr.type == "error" then
self.status = "canceled";
self.callback(self, {});
return;
end
local command = result:get_child("command", xmlns_commands);
self.status = command.attr.status;
self.sessionid = command.attr.sessionid;
self.form = command:get_child("x", xmlns_data);
self.note = command:get_child("note"); --FIXME handle multiple <note/>s
self.callback(self);
end
-- Initial execution of a command
function command_mt:execute()
local iq = verse.iq({ to = self.jid, type = "set" })
:tag("command", { xmlns = xmlns_commands, node = self.command });
self.stream:send_iq(iq, function (result)
self:_process_response(result);
end);
end
function command_mt:next(form)
local iq = verse.iq({ to = self.jid, type = "set" })
:tag("command", {
xmlns = xmlns_commands,
node = self.command,
sessionid = self.sessionid
});
if form then iq:add_child(form); end
self.stream:send_iq(iq, function (result)
self:_process_response(result);
end);
end

Some files were not shown because too many files have changed in this diff Show More