initial
This commit is contained in:
commit
d0e23d516b
|
@ -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()
|
|
@ -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
|
|
@ -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;
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
|
@ -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;
|
||||
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
default
|
|
@ -0,0 +1,2 @@
|
|||
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 368
|
||||
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 default
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
[paths]
|
||||
default = http://code.matthewwild.co.uk/verse
|
|
@ -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.
|
@ -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
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
default
|
|
@ -0,0 +1,3 @@
|
|||
0
|
||||
pull
|
||||
http://code.matthewwild.co.uk/verse
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
local sha1 = require "util.sha1";
|
||||
|
||||
return { sha1 = sha1.sha1 };
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue