mirror of
https://github.com/moparisthebest/mod_xxxx
synced 2024-12-21 06:58:50 -05:00
initial
This commit is contained in:
commit
d0e23d516b
508
dev/mod_ircd.old_comments
Normal file
508
dev/mod_ircd.old_comments
Normal file
@ -0,0 +1,508 @@
|
||||
-- README
|
||||
-- Squish verse into this dir, then squish them into one, which you move
|
||||
-- and rename to mod_ircd.lua in your prosody modules/plugins dir.
|
||||
--
|
||||
-- IRC spec:
|
||||
-- http://tools.ietf.org/html/rfc2812
|
||||
local _module = module
|
||||
module = _G.module
|
||||
local module = _module
|
||||
--
|
||||
local component_jid, component_secret, muc_server, port_number =
|
||||
module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
|
||||
|
||||
if not muc_server then
|
||||
module:log ("error", "You need to set the MUC server! halting.")
|
||||
return false;
|
||||
end
|
||||
|
||||
package.loaded["util.sha1"] = require "util.encodings";
|
||||
local verse = require "verse"
|
||||
require "verse.component"
|
||||
require "socket"
|
||||
c = verse.new();--verse.logger())
|
||||
c:add_plugin("groupchat");
|
||||
|
||||
local function verse2prosody(e)
|
||||
return c:event("stanza", e.stanza) or true;
|
||||
end
|
||||
module:hook("message/bare", verse2prosody);
|
||||
module:hook("message/full", verse2prosody);
|
||||
module:hook("presence/bare", verse2prosody);
|
||||
module:hook("presence/full", verse2prosody);
|
||||
c.type = "component";
|
||||
c.send = core_post_stanza;
|
||||
|
||||
-- This plugin is actually a verse based component, but that mode is currently commented out
|
||||
|
||||
-- Add some hooks for debugging
|
||||
--c:hook("opened", function () print("Stream opened!") end);
|
||||
--c:hook("closed", function () print("Stream closed!") end);
|
||||
--c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
--c:hook("incoming-raw", print, 1000);
|
||||
--c:hook("stanza", print, 1000);
|
||||
--c:hook("outgoing-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
--c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
--c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
--c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
--c.connect_host = "127.0.0.1"
|
||||
--c:connect_component(component_jid, component_secret);
|
||||
|
||||
local jid = require "util.jid";
|
||||
local nodeprep = require "util.encodings".stringprep.nodeprep;
|
||||
|
||||
local function utf8_clean (s)
|
||||
local push, join = table.insert, table.concat;
|
||||
local r, i = {}, 1;
|
||||
if not(s and #s > 0) then
|
||||
return ""
|
||||
end
|
||||
while true do
|
||||
local c = s:sub(i,i)
|
||||
local b = c:byte();
|
||||
local w = (
|
||||
(b >= 9 and b <= 10 and 0) or
|
||||
(b >= 32 and b <= 126 and 0) or
|
||||
(b >= 192 and b <= 223 and 1) or
|
||||
(b >= 224 and b <= 239 and 2) or
|
||||
(b >= 240 and b <= 247 and 3) or
|
||||
(b >= 248 and b <= 251 and 4) or
|
||||
(b >= 251 and b <= 252 and 5) or nil
|
||||
)
|
||||
if not w then
|
||||
push(r, "?")
|
||||
else
|
||||
local n = i + w;
|
||||
if w == 0 then
|
||||
push(r, c);
|
||||
elseif n > #s then
|
||||
push(r, ("?"):format(b));
|
||||
else
|
||||
local e = s:sub(i+1,n);
|
||||
if e:match('^[\128-\191]*$') then
|
||||
push(r, c);
|
||||
push(r, e);
|
||||
i = n;
|
||||
else
|
||||
push(r, ("?"):format(b));
|
||||
end
|
||||
end
|
||||
end
|
||||
i = i + 1;
|
||||
if i > #s then
|
||||
break
|
||||
end
|
||||
end
|
||||
return join(r);
|
||||
end
|
||||
|
||||
local function parse_line(line)
|
||||
local ret = {};
|
||||
if line:sub(1,1) == ":" then
|
||||
ret.from, line = line:match("^:(%w+)%s+(.*)$");
|
||||
end
|
||||
for part in line:gmatch("%S+") do
|
||||
if part:sub(1,1) == ":" then
|
||||
ret[#ret+1] = line:match(":(.*)$");
|
||||
break
|
||||
end
|
||||
ret[#ret+1]=part;
|
||||
end
|
||||
return ret;
|
||||
end
|
||||
|
||||
local function build_line(parts)
|
||||
if #parts > 1 then
|
||||
parts[#parts] = ":" .. parts[#parts];
|
||||
end
|
||||
return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
|
||||
end
|
||||
|
||||
local function irc2muc(channel, nick)
|
||||
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
|
||||
return jid.join(room, muc_server, nick)
|
||||
end
|
||||
local function muc2irc(room)
|
||||
local channel, _, nick = jid.split(room);
|
||||
return "#"..channel, nick;
|
||||
end
|
||||
local role_map = {
|
||||
moderator = "@",
|
||||
participant = "",
|
||||
visitor = "",
|
||||
none = ""
|
||||
}
|
||||
local aff_map = {
|
||||
owner = "~",
|
||||
administrator = "&",
|
||||
member = "+",
|
||||
none = ""
|
||||
}
|
||||
local role_modemap = {
|
||||
moderator = "o",
|
||||
participant = "",
|
||||
visitor = "",
|
||||
none = ""
|
||||
}
|
||||
local aff_modemap = {
|
||||
owner = "q",
|
||||
administrator = "a",
|
||||
member = "v",
|
||||
none = ""
|
||||
}
|
||||
|
||||
local irc_listener = { default_port = port_number, default_mode = "*l" };
|
||||
|
||||
local sessions = {};
|
||||
local jids = {};
|
||||
local commands = {};
|
||||
|
||||
local nicks = {};
|
||||
|
||||
local st = require "util.stanza";
|
||||
|
||||
local conference_server = muc_server;
|
||||
|
||||
local function irc_close_session(session)
|
||||
session.conn:close();
|
||||
end
|
||||
|
||||
function irc_listener.onincoming(conn, data)
|
||||
local session = sessions[conn];
|
||||
if not session then
|
||||
session = { conn = conn, host = component_jid, reset_stream = function () end,
|
||||
close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
|
||||
rooms = {},
|
||||
roster = {} };
|
||||
sessions[conn] = session;
|
||||
function session.data(data)
|
||||
local parts = parse_line(data);
|
||||
module:log("debug", require"util.serialization".serialize(parts));
|
||||
local command = table.remove(parts, 1);
|
||||
if not command then
|
||||
return;
|
||||
end
|
||||
command = command:upper();
|
||||
if not session.nick then
|
||||
if not (command == "USER" or command == "NICK") then
|
||||
module:log("debug", "Client tried to send command %s before registering", command);
|
||||
return session.send{from=muc_server, "451", command, "You have not registered"}
|
||||
end
|
||||
end
|
||||
if commands[command] then
|
||||
local ret = commands[command](session, parts);
|
||||
if ret then
|
||||
return session.send(ret);
|
||||
end
|
||||
else
|
||||
session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
|
||||
return module:log("debug", "Unknown command: %s", command);
|
||||
end
|
||||
end
|
||||
function session.send(data)
|
||||
if type(data) == "string" then
|
||||
return conn:write(data.."\r\n");
|
||||
elseif type(data) == "table" then
|
||||
local line = build_line(data);
|
||||
module:log("debug", line);
|
||||
conn:write(line.."\r\n");
|
||||
end
|
||||
end
|
||||
end
|
||||
if data then
|
||||
session.data(data);
|
||||
end
|
||||
end
|
||||
|
||||
function irc_listener.ondisconnect(conn, error)
|
||||
local session = sessions[conn];
|
||||
if session then
|
||||
for _, room in pairs(session.rooms) do
|
||||
room:leave("Disconnected");
|
||||
end
|
||||
if session.nick then
|
||||
nicks[session.nick] = nil;
|
||||
end
|
||||
if session.full_jid then
|
||||
jids[session.full_jid] = nil;
|
||||
end
|
||||
end
|
||||
sessions[conn] = nil;
|
||||
end
|
||||
|
||||
function commands.NICK(session, args)
|
||||
if session.nick then
|
||||
session.send{from = muc_server, "484", "*", nick, "I'm afraid I can't let you do that"};
|
||||
--TODO Loop throug all rooms and change nick, with help from Verse.
|
||||
return;
|
||||
end
|
||||
local nick = args[1];
|
||||
nick = nick:gsub("[^%w_]","");
|
||||
if nicks[nick] then
|
||||
session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"};
|
||||
return;
|
||||
end
|
||||
local full_jid = jid.join(nick, component_jid, "ircd");
|
||||
jids[full_jid] = session;
|
||||
jids[full_jid]["ar_last"] = {};
|
||||
nicks[nick] = session;
|
||||
session.nick = nick;
|
||||
session.full_jid = full_jid;
|
||||
session.type = "c2s";
|
||||
|
||||
session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick};
|
||||
session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version};
|
||||
session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)}
|
||||
session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")};
|
||||
session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"};
|
||||
session.send{from = muc_server, "372", nick, "-"};
|
||||
session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"};
|
||||
session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."};
|
||||
session.send{from = muc_server, "372", nick, "-"};
|
||||
session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."};
|
||||
session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"};
|
||||
session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"};
|
||||
session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."};
|
||||
|
||||
session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
|
||||
-- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
|
||||
end
|
||||
|
||||
function commands.USER(session, params)
|
||||
-- FIXME
|
||||
-- Empty command for now
|
||||
end
|
||||
|
||||
local function mode_map(am, rm, nicks)
|
||||
local rnick;
|
||||
local c_modes;
|
||||
c_modes = aff_modemap[am]..role_modemap[rm]
|
||||
rnick = string.rep(nicks.." ", c_modes:len())
|
||||
if c_modes == "" then return nil, nil end
|
||||
return c_modes, rnick
|
||||
end
|
||||
|
||||
function commands.JOIN(session, args)
|
||||
local channel = args[1];
|
||||
if not channel then return end
|
||||
local room_jid = irc2muc(channel);
|
||||
print(session.full_jid);
|
||||
if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
|
||||
local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
|
||||
if not room then
|
||||
return ":"..muc_server.." ERR :Could not join room: "..err
|
||||
end
|
||||
session.rooms[channel] = room;
|
||||
room.channel = channel;
|
||||
room.session = session;
|
||||
session.send{from=session.nick, "JOIN", channel};
|
||||
if room.subject then
|
||||
session.send{from=muc_server, 332, session.nick, channel ,room.subject};
|
||||
end
|
||||
commands.NAMES(session, channel);
|
||||
|
||||
room:hook("subject-changed", function(changed)
|
||||
session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or ""));
|
||||
end);
|
||||
|
||||
room:hook("message", function(event)
|
||||
if not event.body then return end
|
||||
local nick, body = event.nick, event.body;
|
||||
if nick ~= session.nick then
|
||||
if body:sub(1,4) == "/me " then
|
||||
body = "\1ACTION ".. body:sub(5) .. "\1"
|
||||
end
|
||||
local type = event.stanza.attr.type;
|
||||
session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body};
|
||||
--FIXME PM's probably won't work
|
||||
end
|
||||
end);
|
||||
|
||||
room:hook("presence", function(ar)
|
||||
local c_modes;
|
||||
local rnick;
|
||||
if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end
|
||||
local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
|
||||
if x_ar then
|
||||
local xar_item = x_ar:get_child("item")
|
||||
if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
|
||||
if xar_item.attr.affiliation and xar_item.attr.role then
|
||||
if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
|
||||
not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
|
||||
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
else
|
||||
c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
|
||||
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, -1);
|
||||
end
|
||||
|
||||
c:hook("groupchat/joined", function(room)
|
||||
local session = room.session or jids[room.opts.source];
|
||||
local channel = "#"..room.jid:match("^(.*)@");
|
||||
session.send{from=session.nick.."!"..session.nick, "JOIN", channel};
|
||||
if room.topic then
|
||||
session.send{from=muc_server, 332, room.topic};
|
||||
end
|
||||
commands.NAMES(session, channel)
|
||||
room:hook("occupant-joined", function(nick)
|
||||
session.send{from=nick.nick.."!"..nick.nick, "JOIN", channel};
|
||||
end);
|
||||
room:hook("occupant-left", function(nick)
|
||||
jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; -- ugly
|
||||
session.send{from=nick.nick.."!"..nick.nick, "PART", channel};
|
||||
end);
|
||||
end);
|
||||
|
||||
function commands.NAMES(session, channel)
|
||||
local nicks = { };
|
||||
local room = session.rooms[channel];
|
||||
local symbols_map = {
|
||||
owner = "~",
|
||||
administrator = "&",
|
||||
moderator = "@",
|
||||
member = "+"
|
||||
}
|
||||
|
||||
if not room then return end
|
||||
-- TODO Break this out into commands.NAMES
|
||||
for nick, n in pairs(room.occupants) do
|
||||
if n.affiliation == "owner" and n.role == "moderator" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "administrator" and n.role == "moderator" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "member" and n.role == "moderator" then
|
||||
nick = symbols_map[n.role]..nick;
|
||||
elseif n.affiliation == "member" and n.role == "partecipant" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "none" and n.role == "moderator" then
|
||||
nick = symbols_map[n.role]..nick;
|
||||
end
|
||||
table.insert(nicks, nick);
|
||||
end
|
||||
nicks = table.concat(nicks, " ");
|
||||
session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks));
|
||||
session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel));
|
||||
session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks);
|
||||
end
|
||||
|
||||
function commands.PART(session, args)
|
||||
local channel, part_message = unpack(args);
|
||||
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
|
||||
if not room then return end
|
||||
channel = channel:match("^([%S]*)");
|
||||
session.rooms[channel]:leave(part_message);
|
||||
jids[session.full_jid].ar_last[room.."@"..muc_server] = nil;
|
||||
session.send(":"..session.nick.." PART :"..channel);
|
||||
end
|
||||
|
||||
function commands.PRIVMSG(session, args)
|
||||
local channel, message = unpack(args);
|
||||
if message and #message > 0 then
|
||||
if message:sub(1,8) == "\1ACTION " then
|
||||
message = "/me ".. message:sub(9,-2)
|
||||
end
|
||||
message = utf8_clean(message);
|
||||
if channel:sub(1,1) == "#" then
|
||||
if session.rooms[channel] then
|
||||
module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel);
|
||||
session.rooms[channel]:send_message(message);
|
||||
end
|
||||
else -- private message
|
||||
local nick = channel;
|
||||
module:log("debug", "PM to %s", nick);
|
||||
for channel, room in pairs(session.rooms) do
|
||||
module:log("debug", "looking for %s in %s", nick, channel);
|
||||
if room.occupants[nick] then
|
||||
module:log("debug", "found %s in %s", nick, channel);
|
||||
local who = room.occupants[nick];
|
||||
-- FIXME PMs in verse
|
||||
--room:send_private_message(nick, message);
|
||||
local pm = st.message({type="chat",to=who.jid}, message);
|
||||
module:log("debug", "sending PM to %s: %s", nick, tostring(pm));
|
||||
room:send(pm)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function commands.PING(session, args)
|
||||
session.send{from=muc_server, "PONG", args[1]};
|
||||
end
|
||||
|
||||
function commands.TOPIC(session, message)
|
||||
if not message then return end
|
||||
local channel, topic = message[1], message[2];
|
||||
channel = utf8_clean(channel);
|
||||
topic = utf8_clean(topic);
|
||||
if not channel then return end
|
||||
local room = session.rooms[channel];
|
||||
|
||||
if topic then room:set_subject(topic); end
|
||||
end
|
||||
|
||||
function commands.WHO(session, args)
|
||||
local channel = args[1];
|
||||
if session.rooms[channel] then
|
||||
local room = session.rooms[channel]
|
||||
for nick in pairs(room.occupants) do
|
||||
--n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild
|
||||
session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
|
||||
end
|
||||
session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
|
||||
end
|
||||
end
|
||||
|
||||
function commands.MODE(session, args) -- FIXME
|
||||
-- emptied for the time being, until something sane which works is available.
|
||||
end
|
||||
|
||||
function commands.QUIT(session, args)
|
||||
session.send{"ERROR", "Closing Link: "..session.nick};
|
||||
for _, room in pairs(session.rooms) do
|
||||
room:leave(args[1]);
|
||||
end
|
||||
jids[session.full_jid] = nil;
|
||||
nicks[session.nick] = nil;
|
||||
sessions[session.conn] = nil;
|
||||
session:close();
|
||||
end
|
||||
|
||||
function commands.RAW(session, data)
|
||||
--c:send(data)
|
||||
end
|
||||
|
||||
local function desetup()
|
||||
require "net.connlisteners".deregister("irc");
|
||||
end
|
||||
|
||||
--c:hook("ready", function ()
|
||||
require "net.connlisteners".register("irc", irc_listener);
|
||||
require "net.connlisteners".start("irc");
|
||||
--end);
|
||||
|
||||
module:hook("module-unloaded", desetup)
|
||||
|
||||
|
||||
--print("Starting loop...")
|
||||
--verse.loop()
|
8
ircd.sh
Executable file
8
ircd.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
squish
|
||||
echo -e 'config:reload()\nmodule:reload("ircd", "burtrum.org")' | telnet localhost 5582
|
||||
|
||||
exit
|
||||
|
||||
echo -e 'module:load("ircd", "burtrum.org")' | telnet localhost 5582
|
598
mod_ircd.in.lua
Normal file
598
mod_ircd.in.lua
Normal file
@ -0,0 +1,598 @@
|
||||
-- README
|
||||
-- Squish verse into this dir, then squish them into one, which you move
|
||||
-- and rename to mod_ircd.lua in your prosody modules/plugins dir.
|
||||
--
|
||||
-- IRC spec:
|
||||
-- http://tools.ietf.org/html/rfc2812
|
||||
|
||||
local client_xmlns = "jabber:client"
|
||||
|
||||
--module:set_global();
|
||||
|
||||
local server = require "net.server";
|
||||
local portmanager = require "core.portmanager";
|
||||
|
||||
local component_jid, component_secret, muc_server, port_number =
|
||||
module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
|
||||
|
||||
if not muc_server then
|
||||
module:log ("error", "You need to set the MUC server in the configuration (conference_server)!")
|
||||
module:log ("error", "Be a good boy or girl and go read the wiki at: http://code.google.com/p/prosody-modules/wiki/mod_ircd")
|
||||
return false;
|
||||
end
|
||||
|
||||
package.loaded["util.sha1"] = require "util.encodings";
|
||||
--local verse = require "verse"
|
||||
--require "verse.component"
|
||||
--require "socket"
|
||||
require "verse".init("component");
|
||||
|
||||
c = verse.new(); -- something interferes with prosody's console logging
|
||||
c:add_plugin("groupchat");
|
||||
|
||||
local function verse2prosody(e)
|
||||
return c:event("stanza", e.stanza) or true;
|
||||
end
|
||||
module:hook("message/bare", verse2prosody);
|
||||
module:hook("message/full", verse2prosody);
|
||||
module:hook("presence/bare", verse2prosody);
|
||||
module:hook("presence/full", verse2prosody);
|
||||
c.type = "component";
|
||||
c.send = core_post_stanza;
|
||||
|
||||
local jid = require "util.jid";
|
||||
local nodeprep = require "util.encodings".stringprep.nodeprep;
|
||||
|
||||
local function utf8_clean (s)
|
||||
local push, join = table.insert, table.concat;
|
||||
local r, i = {}, 1;
|
||||
if not(s and #s > 0) then
|
||||
return ""
|
||||
end
|
||||
while true do
|
||||
local c = s:sub(i,i)
|
||||
local b = c:byte();
|
||||
local w = (
|
||||
(b >= 9 and b <= 10 and 0) or
|
||||
(b >= 32 and b <= 126 and 0) or
|
||||
(b >= 192 and b <= 223 and 1) or
|
||||
(b >= 224 and b <= 239 and 2) or
|
||||
(b >= 240 and b <= 247 and 3) or
|
||||
(b >= 248 and b <= 251 and 4) or
|
||||
(b >= 251 and b <= 252 and 5) or nil
|
||||
)
|
||||
if not w then
|
||||
push(r, "?")
|
||||
else
|
||||
local n = i + w;
|
||||
if w == 0 then
|
||||
push(r, c);
|
||||
elseif n > #s then
|
||||
push(r, ("?"):format(b));
|
||||
else
|
||||
local e = s:sub(i+1,n);
|
||||
if e:match('^[\128-\191]*$') then
|
||||
push(r, c);
|
||||
push(r, e);
|
||||
i = n;
|
||||
else
|
||||
push(r, ("?"):format(b));
|
||||
end
|
||||
end
|
||||
end
|
||||
i = i + 1;
|
||||
if i > #s then
|
||||
break
|
||||
end
|
||||
end
|
||||
return join(r);
|
||||
end
|
||||
|
||||
local function parse_line(line)
|
||||
local ret = {};
|
||||
if line:sub(1,1) == ":" then
|
||||
ret.from, line = line:match("^:(%w+)%s+(.*)$");
|
||||
end
|
||||
for part in line:gmatch("%S+") do
|
||||
if part:sub(1,1) == ":" then
|
||||
ret[#ret+1] = line:match(":(.*)$");
|
||||
break
|
||||
end
|
||||
ret[#ret+1]=part;
|
||||
end
|
||||
return ret;
|
||||
end
|
||||
|
||||
local function build_line(parts)
|
||||
if #parts > 1 then
|
||||
parts[#parts] = ":" .. parts[#parts];
|
||||
end
|
||||
return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
|
||||
end
|
||||
|
||||
local function irc2muc(channel, nick)
|
||||
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
|
||||
if not nick then
|
||||
return jid.join(room, muc_server);
|
||||
else
|
||||
return jid.join(room, muc_server, nick);
|
||||
end
|
||||
end
|
||||
local function muc2irc(room)
|
||||
local channel, _, nick = jid.split(room);
|
||||
return "#"..channel, nick;
|
||||
end
|
||||
local role_map = {
|
||||
moderator = "@",
|
||||
participant = "",
|
||||
visitor = "",
|
||||
none = ""
|
||||
}
|
||||
local aff_map = {
|
||||
owner = "~",
|
||||
administrator = "&",
|
||||
member = "+",
|
||||
none = ""
|
||||
}
|
||||
local role_modemap = {
|
||||
moderator = "o",
|
||||
participant = "",
|
||||
visitor = "",
|
||||
none = ""
|
||||
}
|
||||
local aff_modemap = {
|
||||
owner = "q",
|
||||
administrator = "a",
|
||||
member = "v",
|
||||
none = ""
|
||||
}
|
||||
|
||||
local irc_listener = { default_port = port_number, default_mode = "*l"; interface = "192.168.1.3" };
|
||||
|
||||
local sessions = module:shared("sessions");
|
||||
local jids = {};
|
||||
local commands = {};
|
||||
|
||||
local nicks = {};
|
||||
local usernames = {};
|
||||
|
||||
local st = require "util.stanza";
|
||||
|
||||
local conference_server = muc_server;
|
||||
|
||||
local function irc_close_session(session)
|
||||
session.conn:close();
|
||||
end
|
||||
|
||||
function irc_listener.onincoming(conn, data)
|
||||
local session = sessions[conn];
|
||||
if not session then
|
||||
session = { conn = conn, host = component_jid, reset_stream = function () end,
|
||||
close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
|
||||
rooms = {}, roster = {}, has_un = false };
|
||||
sessions[conn] = session;
|
||||
|
||||
function session.data(data)
|
||||
local parts = parse_line(data);
|
||||
module:log("debug", require"util.serialization".serialize(parts));
|
||||
local command = table.remove(parts, 1);
|
||||
if not command then
|
||||
return;
|
||||
end
|
||||
command = command:upper();
|
||||
if not session.username and not session.nick then
|
||||
if not (command == "USER" or command == "NICK") then
|
||||
module:log("debug", "Client tried to send command %s before registering", command);
|
||||
return session.send{from=muc_server, "451", command, "You have not completed the registration."}
|
||||
end
|
||||
end
|
||||
if commands[command] then
|
||||
local ret = commands[command](session, parts);
|
||||
if ret then
|
||||
return session.send(ret);
|
||||
end
|
||||
else
|
||||
session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
|
||||
return module:log("debug", "Unknown command: %s", command);
|
||||
end
|
||||
end
|
||||
|
||||
function session.send(data)
|
||||
if type(data) == "string" then
|
||||
return conn:write(data.."\r\n");
|
||||
elseif type(data) == "table" then
|
||||
local line = build_line(data);
|
||||
module:log("debug", line);
|
||||
conn:write(line.."\r\n");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if data then
|
||||
session.data(data);
|
||||
end
|
||||
end
|
||||
|
||||
function irc_listener.ondisconnect(conn, error)
|
||||
local session = sessions[conn];
|
||||
|
||||
if session then
|
||||
for _, room in pairs(session.rooms) do
|
||||
room:leave("Disconnected");
|
||||
end
|
||||
if session.nick then
|
||||
nicks[session.nick] = nil;
|
||||
end
|
||||
if session.full_jid then
|
||||
jids[session.full_jid] = nil;
|
||||
end
|
||||
if session.username then
|
||||
usernames[session.username] = nil;
|
||||
end
|
||||
end
|
||||
sessions[conn] = nil;
|
||||
end
|
||||
|
||||
local function nick_inuse(nick)
|
||||
if nicks[nick] then return true else return false end
|
||||
end
|
||||
local function check_username(un)
|
||||
local count = 0;
|
||||
local result;
|
||||
|
||||
for name, given in pairs(usernames) do
|
||||
if un == given then count = count + 1; end
|
||||
end
|
||||
|
||||
result = count + 1;
|
||||
|
||||
if count > 0 then return tostring(un)..tostring(result); else return tostring(un); end
|
||||
end
|
||||
local function set_t_data(session, full_jid)
|
||||
session.full_jid = full_jid;
|
||||
jids[full_jid] = session;
|
||||
jids[full_jid]["ar_last"] = {};
|
||||
jids[full_jid]["nicks_changing"] = {};
|
||||
|
||||
if session.nick then nicks[session.nick] = session; end
|
||||
end
|
||||
local function send_motd(session)
|
||||
local nick = session.nick;
|
||||
|
||||
if session.username and session.nick then -- send MOTD only if username and nick are set
|
||||
session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick};
|
||||
session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version};
|
||||
session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)}
|
||||
session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")};
|
||||
session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "CHANTYPES=# PREFIX=(qaov)~&@+", "are supported by this server"));
|
||||
session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "STATUSMSG=~&@+", "are supported by this server"));
|
||||
session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"};
|
||||
session.send{from = muc_server, "372", nick, "-"};
|
||||
session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"};
|
||||
session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."};
|
||||
session.send{from = muc_server, "372", nick, "-"};
|
||||
session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."};
|
||||
session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"};
|
||||
session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"};
|
||||
session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."};
|
||||
session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
|
||||
end -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
|
||||
end
|
||||
|
||||
function commands.NICK(session, args)
|
||||
local nick = args[1];
|
||||
nick = nick:gsub("[^%w_]","");
|
||||
|
||||
if session.nick and not nick_inuse(nick) then -- changing nick
|
||||
local oldnick = session.nick;
|
||||
|
||||
-- update and replace session data
|
||||
session.nick = nick;
|
||||
nicks[oldnick] = nil;
|
||||
nicks[nick] = session;
|
||||
|
||||
session.send{from=oldnick.."!"..nicks[nick].username, "NICK", nick};
|
||||
|
||||
-- broadcast changes if required
|
||||
if session.rooms then
|
||||
session.nicks_changing[nick] = { oldnick, session.username };
|
||||
|
||||
for id, room in pairs(session.rooms) do room:change_nick(nick); end
|
||||
|
||||
session.nicks_changing[nick] = nil;
|
||||
end
|
||||
|
||||
return;
|
||||
elseif nick_inuse(nick) then
|
||||
session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return;
|
||||
end
|
||||
|
||||
session.nick = nick;
|
||||
session.type = "c2s";
|
||||
nicks[nick] = session;
|
||||
|
||||
-- Some choppy clients send in NICK before USER, that needs to be handled
|
||||
if session.username then
|
||||
set_t_data(session, jid.join(session.username, component_jid, "ircd"));
|
||||
end
|
||||
|
||||
send_motd(session);
|
||||
end
|
||||
|
||||
function commands.USER(session, params)
|
||||
local username = params[1];
|
||||
|
||||
if not session.has_un then
|
||||
local un_checked = check_username(username);
|
||||
|
||||
usernames[un_checked] = username;
|
||||
session.username = un_checked;
|
||||
session.has_un = true;
|
||||
|
||||
if not session.full_jid then
|
||||
set_t_data(session, jid.join(session.username, component_jid, "ircd"));
|
||||
end
|
||||
else
|
||||
return session.send{from=muc_server, "462", "USER", "You may not re-register."}
|
||||
end
|
||||
|
||||
send_motd(session);
|
||||
end
|
||||
|
||||
function commands.USERHOST(session, params) -- can show only users on the gateway. Needed for some clients to determinate self hostmask.
|
||||
local nick = params[1];
|
||||
|
||||
if not nick then session.send{from=muc_server, "461", "USERHOST", "Not enough parameters"}; return; end
|
||||
|
||||
if nicks[nick] and nicks[nick].nick and nicks[nick].username then
|
||||
session.send{from=muc_server, "302", session.nick, nick.."=+"..nicks[nick].username}; return;
|
||||
else
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
local function mode_map(am, rm, nicks)
|
||||
local rnick;
|
||||
local c_modes;
|
||||
c_modes = aff_modemap[am]..role_modemap[rm]
|
||||
rnick = string.rep(nicks.." ", c_modes:len())
|
||||
if c_modes == "" then return nil, nil end
|
||||
return c_modes, rnick
|
||||
end
|
||||
|
||||
function commands.JOIN(session, args)
|
||||
local channel = args[1];
|
||||
if not channel then return end
|
||||
local room_jid = irc2muc(channel);
|
||||
|
||||
if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
|
||||
local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
|
||||
if not room then
|
||||
return ":"..muc_server.." ERR :Could not join room: "..err
|
||||
end
|
||||
|
||||
session.rooms[channel] = room;
|
||||
room.session = session;
|
||||
|
||||
if session.nicks_changing[session.nick] then -- my own nick is changing
|
||||
commands.NAMES(session, channel);
|
||||
else
|
||||
session.send{from=session.nick.."!"..session.username, "JOIN", channel};
|
||||
if room.subject then
|
||||
session.send{from=muc_server, 332, session.nick, channel, room.subject};
|
||||
end
|
||||
commands.NAMES(session, channel);
|
||||
end
|
||||
|
||||
room:hook("subject-changed", function(changed)
|
||||
session.send{from=changed.by.nick, "TOPIC", channel, changed.to or ""}
|
||||
end);
|
||||
|
||||
room:hook("message", function(event)
|
||||
if not event.body then return end
|
||||
local nick, body = event.nick, event.body;
|
||||
if nick ~= session.nick then
|
||||
if body:sub(1,4) == "/me " then
|
||||
body = "\1ACTION ".. body:sub(5) .. "\1"
|
||||
end
|
||||
local type = event.stanza.attr.type;
|
||||
session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body};
|
||||
--FIXME PM's probably won't work
|
||||
end
|
||||
end);
|
||||
|
||||
room:hook("presence", function(ar)
|
||||
local c_modes;
|
||||
local rnick;
|
||||
if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end
|
||||
local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
|
||||
if x_ar then
|
||||
local xar_item = x_ar:get_child("item")
|
||||
if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
|
||||
if xar_item.attr.affiliation and xar_item.attr.role then
|
||||
if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
|
||||
not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
|
||||
n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
|
||||
if n_self_changing then return; end
|
||||
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
else
|
||||
c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
|
||||
jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
|
||||
n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
|
||||
if n_self_changing then return; end
|
||||
c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
|
||||
if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, -1);
|
||||
end
|
||||
|
||||
c:hook("groupchat/joined", function(room)
|
||||
local session = room.session or jids[room.opts.source];
|
||||
local channel = "#"..room.jid:match("^(.*)@");
|
||||
|
||||
room:hook("occupant-joined", function(nick)
|
||||
if session.nicks_changing[nick.nick] then
|
||||
session.send{from=session.nicks_changing[nick.nick][1].."!"..(session.nicks_changing[nick.nick][2] or "xmpp"), "NICK", nick.nick};
|
||||
session.nicks_changing[nick.nick] = nil;
|
||||
else
|
||||
session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "JOIN", channel};
|
||||
end
|
||||
end);
|
||||
room:hook("occupant-left", function(nick)
|
||||
if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end
|
||||
local status_code =
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code;
|
||||
|
||||
|
||||
if status_code == "303" then
|
||||
local newnick =
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item") and
|
||||
nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item").attr.nick;
|
||||
|
||||
session.nicks_changing[newnick] = { nick.nick, (nicks[nick.nick] and nicks[nick.nick].username or "xmpp") }; return;
|
||||
end
|
||||
|
||||
for id, data in pairs(session.nicks_changing) do
|
||||
if data[1] == nick.nick then return; end
|
||||
end
|
||||
session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "PART", channel};
|
||||
end);
|
||||
end);
|
||||
|
||||
function commands.NAMES(session, channel)
|
||||
local nicks = { };
|
||||
if type(channel) == "table" then channel = channel[1] end
|
||||
|
||||
local room = session.rooms[channel];
|
||||
|
||||
local symbols_map = {
|
||||
owner = "~",
|
||||
administrator = "&",
|
||||
moderator = "@",
|
||||
member = "+"
|
||||
}
|
||||
|
||||
if not room then return end
|
||||
-- TODO Break this out into commands.NAMES
|
||||
for nick, n in pairs(room.occupants) do
|
||||
if n.affiliation == "owner" and n.role == "moderator" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "administrator" and n.role == "moderator" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "member" and n.role == "moderator" then
|
||||
nick = symbols_map[n.role]..nick;
|
||||
elseif n.affiliation == "member" and n.role == "partecipant" then
|
||||
nick = symbols_map[n.affiliation]..nick;
|
||||
elseif n.affiliation == "none" and n.role == "moderator" then
|
||||
nick = symbols_map[n.role]..nick;
|
||||
end
|
||||
table.insert(nicks, nick);
|
||||
end
|
||||
nicks = table.concat(nicks, " ");
|
||||
session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks));
|
||||
session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel));
|
||||
session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks);
|
||||
end
|
||||
|
||||
function commands.PART(session, args)
|
||||
local channel, part_message = unpack(args);
|
||||
local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
|
||||
if not room then return end
|
||||
channel = channel:match("^([%S]*)");
|
||||
session.rooms[channel]:leave(part_message);
|
||||
jids[session.full_jid].ar_last[room.."@"..muc_server] = nil;
|
||||
session.send{from=session.nick.."!"..session.username, "PART", channel};
|
||||
end
|
||||
|
||||
function commands.PRIVMSG(session, args)
|
||||
local channel, message = unpack(args);
|
||||
if message and #message > 0 then
|
||||
if message:sub(1,8) == "\1ACTION " then
|
||||
message = "/me ".. message:sub(9,-2)
|
||||
end
|
||||
message = utf8_clean(message);
|
||||
if channel:sub(1,1) == "#" then
|
||||
if session.rooms[channel] then
|
||||
module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel);
|
||||
session.rooms[channel]:send_message(message);
|
||||
end
|
||||
else -- private message
|
||||
local nick = channel;
|
||||
module:log("debug", "PM to %s", nick);
|
||||
for channel, room in pairs(session.rooms) do
|
||||
module:log("debug", "looking for %s in %s", nick, channel);
|
||||
if room.occupants[nick] then
|
||||
module:log("debug", "found %s in %s", nick, channel);
|
||||
local who = room.occupants[nick];
|
||||
-- FIXME PMs in verse
|
||||
--room:send_private_message(nick, message);
|
||||
local pm = st.message({type="chat",to=who.jid}, message);
|
||||
module:log("debug", "sending PM to %s: %s", nick, tostring(pm));
|
||||
room:send(pm)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function commands.PING(session, args)
|
||||
session.send{from=muc_server, "PONG", args[1]};
|
||||
end
|
||||
|
||||
function commands.TOPIC(session, message)
|
||||
if not message then return end
|
||||
local channel, topic = message[1], message[2];
|
||||
channel = utf8_clean(channel);
|
||||
topic = utf8_clean(topic);
|
||||
if not channel then return end
|
||||
local room = session.rooms[channel];
|
||||
|
||||
if topic then room:set_subject(topic); end
|
||||
end
|
||||
|
||||
function commands.WHO(session, args)
|
||||
local channel = args[1];
|
||||
if session.rooms[channel] then
|
||||
local room = session.rooms[channel]
|
||||
for nick in pairs(room.occupants) do
|
||||
session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
|
||||
end
|
||||
session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
|
||||
end
|
||||
end
|
||||
|
||||
function commands.MODE(session, args) -- Empty command
|
||||
end
|
||||
|
||||
function commands.QUIT(session, args)
|
||||
session.send{"ERROR", "Closing Link: "..session.nick};
|
||||
for _, room in pairs(session.rooms) do
|
||||
room:leave(args[1]);
|
||||
end
|
||||
jids[session.full_jid] = nil;
|
||||
nicks[session.nick] = nil;
|
||||
usernames[session.username] = nil;
|
||||
sessions[session.conn] = nil;
|
||||
session:close();
|
||||
end
|
||||
|
||||
function commands.RAW(session, data) -- Empty command
|
||||
end
|
||||
|
||||
module:provides("net", {
|
||||
name = "ircd";
|
||||
listener = irc_listener;
|
||||
default_port = 7000;
|
||||
});
|
8174
mod_ircd.lua
Normal file
8174
mod_ircd.lua
Normal file
File diff suppressed because it is too large
Load Diff
12
squishy
Normal file
12
squishy
Normal file
@ -0,0 +1,12 @@
|
||||
Output "mod_ircd.lua"
|
||||
|
||||
verse_path = "verse"
|
||||
|
||||
Module "verse" (verse_path.."/verse.lua")
|
||||
|
||||
--Module "verse" (verse_path.."/init.lua")
|
||||
--Module "verse.component" (verse_path.."/component.lua")
|
||||
--Module "verse.plugins.groupchat" (verse_path.."/plugins/groupchat.lua")
|
||||
--Module "verse.plugins.presence" (verse_path.."/plugins/presence.lua")
|
||||
|
||||
Main "mod_ircd.in.lua"
|
580
verse.orig/verse.lua
Normal file
580
verse.orig/verse.lua
Normal file
@ -0,0 +1,580 @@
|
||||
package.preload['verse.plugins.presence'] = (function (...)
|
||||
function verse.plugins.presence(stream)
|
||||
stream.last_presence = nil;
|
||||
|
||||
stream:hook("presence-out", function (presence)
|
||||
if not presence.attr.to then
|
||||
stream.last_presence = presence; -- Cache non-directed presence
|
||||
end
|
||||
end, 1);
|
||||
|
||||
function stream:resend_presence()
|
||||
if last_presence then
|
||||
stream:send(last_presence);
|
||||
end
|
||||
end
|
||||
|
||||
function stream:set_status(opts)
|
||||
local p = verse.presence();
|
||||
if type(opts) == "table" then
|
||||
if opts.show then
|
||||
p:tag("show"):text(opts.show):up();
|
||||
end
|
||||
if opts.prio then
|
||||
p:tag("priority"):text(tostring(opts.prio)):up();
|
||||
end
|
||||
if opts.msg then
|
||||
p:tag("status"):text(opts.msg):up();
|
||||
end
|
||||
end
|
||||
-- TODO maybe use opts as prio if it's a int,
|
||||
-- or as show or status if it's a string?
|
||||
|
||||
stream:send(p);
|
||||
end
|
||||
end
|
||||
end)
|
||||
package.preload['verse.plugins.groupchat'] = (function (...)
|
||||
local events = require "util.events";
|
||||
|
||||
local room_mt = {};
|
||||
room_mt.__index = room_mt;
|
||||
|
||||
local xmlns_delay = "urn:xmpp:delay";
|
||||
local xmlns_muc = "http://jabber.org/protocol/muc";
|
||||
|
||||
function verse.plugins.groupchat(stream)
|
||||
stream:add_plugin("presence")
|
||||
stream.rooms = {};
|
||||
|
||||
stream:hook("stanza", function (stanza)
|
||||
local room_jid = jid.bare(stanza.attr.from);
|
||||
if not room_jid then return end
|
||||
local room = stream.rooms[room_jid]
|
||||
if not room and stanza.attr.to and room_jid then
|
||||
room = stream.rooms[stanza.attr.to.." "..room_jid]
|
||||
end
|
||||
if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
|
||||
if room then
|
||||
local nick = select(3, jid.split(stanza.attr.from));
|
||||
local body = stanza:get_child_text("body");
|
||||
local delay = stanza:get_child("delay", xmlns_delay);
|
||||
local event = {
|
||||
room_jid = room_jid;
|
||||
room = room;
|
||||
sender = room.occupants[nick];
|
||||
nick = nick;
|
||||
body = body;
|
||||
stanza = stanza;
|
||||
delay = (delay and delay.attr.stamp);
|
||||
};
|
||||
local ret = room:event(stanza.name, event);
|
||||
return ret or (stanza.name == "message") or nil;
|
||||
end
|
||||
end, 500);
|
||||
|
||||
function stream:join_room(jid, nick, opts)
|
||||
if not nick then
|
||||
return false, "no nickname supplied"
|
||||
end
|
||||
opts = opts or {};
|
||||
local room = setmetatable({
|
||||
stream = stream, jid = jid, nick = nick,
|
||||
subject = nil,
|
||||
occupants = {},
|
||||
opts = opts,
|
||||
events = events.new()
|
||||
}, room_mt);
|
||||
if opts.source then
|
||||
self.rooms[opts.source.." "..jid] = room;
|
||||
else
|
||||
self.rooms[jid] = room;
|
||||
end
|
||||
local occupants = room.occupants;
|
||||
room:hook("presence", function (presence)
|
||||
local nick = presence.nick or nick;
|
||||
if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
|
||||
occupants[nick] = {
|
||||
nick = nick;
|
||||
jid = presence.stanza.attr.from;
|
||||
presence = presence.stanza;
|
||||
};
|
||||
local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
|
||||
if x then
|
||||
local x_item = x:get_child("item");
|
||||
if x_item and x_item.attr then
|
||||
occupants[nick].real_jid = x_item.attr.jid;
|
||||
occupants[nick].affiliation = x_item.attr.affiliation;
|
||||
occupants[nick].role = x_item.attr.role;
|
||||
end
|
||||
--TODO Check for status 100?
|
||||
end
|
||||
if nick == room.nick then
|
||||
room.stream:event("groupchat/joined", room);
|
||||
else
|
||||
room:event("occupant-joined", occupants[nick]);
|
||||
end
|
||||
elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
|
||||
if nick == room.nick then
|
||||
room.stream:event("groupchat/left", room);
|
||||
if room.opts.source then
|
||||
self.rooms[room.opts.source.." "..jid] = nil;
|
||||
else
|
||||
self.rooms[jid] = nil;
|
||||
end
|
||||
else
|
||||
occupants[nick].presence = presence.stanza;
|
||||
room:event("occupant-left", occupants[nick]);
|
||||
occupants[nick] = nil;
|
||||
end
|
||||
end
|
||||
end);
|
||||
room:hook("message", function(event)
|
||||
local subject = event.stanza:get_child_text("subject");
|
||||
if not subject then return end
|
||||
subject = #subject > 0 and subject or nil;
|
||||
if subject ~= room.subject then
|
||||
local old_subject = room.subject;
|
||||
room.subject = subject;
|
||||
return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
|
||||
end
|
||||
end, 2000);
|
||||
local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
|
||||
self:event("pre-groupchat/joining", join_st);
|
||||
room:send(join_st)
|
||||
self:event("groupchat/joining", room);
|
||||
return room;
|
||||
end
|
||||
|
||||
stream:hook("presence-out", function(presence)
|
||||
if not presence.attr.to then
|
||||
for _, room in pairs(stream.rooms) do
|
||||
room:send(presence);
|
||||
end
|
||||
presence.attr.to = nil;
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
function room_mt:send(stanza)
|
||||
if stanza.name == "message" and not stanza.attr.type then
|
||||
stanza.attr.type = "groupchat";
|
||||
end
|
||||
if stanza.name == "presence" then
|
||||
stanza.attr.to = self.jid .."/"..self.nick;
|
||||
end
|
||||
if stanza.attr.type == "groupchat" or not stanza.attr.to then
|
||||
stanza.attr.to = self.jid;
|
||||
end
|
||||
if self.opts.source then
|
||||
stanza.attr.from = self.opts.source
|
||||
end
|
||||
self.stream:send(stanza);
|
||||
end
|
||||
|
||||
function room_mt:send_message(text)
|
||||
self:send(verse.message():tag("body"):text(text));
|
||||
end
|
||||
|
||||
function room_mt:set_subject(text)
|
||||
self:send(verse.message():tag("subject"):text(text));
|
||||
end
|
||||
|
||||
function room_mt:change_nick(new)
|
||||
self.nick = new;
|
||||
self:send(verse.presence());
|
||||
end
|
||||
|
||||
function room_mt:leave(message)
|
||||
self.stream:event("groupchat/leaving", self);
|
||||
self:send(verse.presence({type="unavailable"}));
|
||||
end
|
||||
|
||||
function room_mt:admin_set(nick, what, value, reason)
|
||||
self:send(verse.iq({type="set"})
|
||||
:query(xmlns_muc .. "#admin")
|
||||
:tag("item", {nick = nick, [what] = value})
|
||||
:tag("reason"):text(reason or ""));
|
||||
end
|
||||
|
||||
function room_mt:set_role(nick, role, reason)
|
||||
self:admin_set(nick, "role", role, reason);
|
||||
end
|
||||
|
||||
function room_mt:set_affiliation(nick, affiliation, reason)
|
||||
self:admin_set(nick, "affiliation", affiliation, reason);
|
||||
end
|
||||
|
||||
function room_mt:kick(nick, reason)
|
||||
self:set_role(nick, "none", reason);
|
||||
end
|
||||
|
||||
function room_mt:ban(nick, reason)
|
||||
self:set_affiliation(nick, "outcast", reason);
|
||||
end
|
||||
|
||||
function room_mt:event(name, arg)
|
||||
self.stream:debug("Firing room event: %s", name);
|
||||
return self.events.fire_event(name, arg);
|
||||
end
|
||||
|
||||
function room_mt:hook(name, callback, priority)
|
||||
return self.events.add_handler(name, callback, priority);
|
||||
end
|
||||
end)
|
||||
package.preload['verse.component'] = (function (...)
|
||||
local verse = require "verse";
|
||||
local stream = verse.stream_mt;
|
||||
|
||||
local jid_split = require "util.jid".split;
|
||||
local lxp = require "lxp";
|
||||
local st = require "util.stanza";
|
||||
local sha1 = require "util.sha1".sha1;
|
||||
|
||||
-- Shortcuts to save having to load util.stanza
|
||||
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
|
||||
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
|
||||
|
||||
local new_xmpp_stream = require "util.xmppstream".new;
|
||||
|
||||
local xmlns_stream = "http://etherx.jabber.org/streams";
|
||||
local xmlns_component = "jabber:component:accept";
|
||||
|
||||
local stream_callbacks = {
|
||||
stream_ns = xmlns_stream,
|
||||
stream_tag = "stream",
|
||||
default_ns = xmlns_component };
|
||||
|
||||
function stream_callbacks.streamopened(stream, attr)
|
||||
stream.stream_id = attr.id;
|
||||
if not stream:event("opened", attr) then
|
||||
stream.notopen = nil;
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream_callbacks.streamclosed(stream)
|
||||
return stream:event("closed");
|
||||
end
|
||||
|
||||
function stream_callbacks.handlestanza(stream, stanza)
|
||||
if stanza.attr.xmlns == xmlns_stream then
|
||||
return stream:event("stream-"..stanza.name, stanza);
|
||||
elseif stanza.attr.xmlns or stanza.name == "handshake" then
|
||||
return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
|
||||
end
|
||||
|
||||
return stream:event("stanza", stanza);
|
||||
end
|
||||
|
||||
function stream:reset()
|
||||
if self.stream then
|
||||
self.stream:reset();
|
||||
else
|
||||
self.stream = new_xmpp_stream(self, stream_callbacks);
|
||||
end
|
||||
self.notopen = true;
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream:connect_component(jid, pass)
|
||||
self.jid, self.password = jid, pass;
|
||||
self.username, self.host, self.resource = jid_split(jid);
|
||||
|
||||
function self.data(conn, data)
|
||||
local ok, err = self.stream:feed(data);
|
||||
if ok then return; end
|
||||
stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
|
||||
stream:close("xml-not-well-formed");
|
||||
end
|
||||
|
||||
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
|
||||
|
||||
self.curr_id = 0;
|
||||
|
||||
self.tracked_iqs = {};
|
||||
self:hook("stanza", function (stanza)
|
||||
local id, type = stanza.attr.id, stanza.attr.type;
|
||||
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
|
||||
self.tracked_iqs[id](stanza);
|
||||
self.tracked_iqs[id] = nil;
|
||||
return true;
|
||||
end
|
||||
end);
|
||||
|
||||
self:hook("stanza", function (stanza)
|
||||
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
|
||||
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
|
||||
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
|
||||
if xmlns then
|
||||
ret = self:event("iq/"..xmlns, stanza);
|
||||
if not ret then
|
||||
ret = self:event("iq", stanza);
|
||||
end
|
||||
end
|
||||
if ret == nil then
|
||||
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
|
||||
return true;
|
||||
end
|
||||
else
|
||||
ret = self:event(stanza.name, stanza);
|
||||
end
|
||||
end
|
||||
return ret;
|
||||
end, -1);
|
||||
|
||||
self:hook("opened", function (attr)
|
||||
print(self.jid, self.stream_id, attr.id);
|
||||
local token = sha1(self.stream_id..pass, true);
|
||||
|
||||
self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
|
||||
self:hook("stream/"..xmlns_component, function (stanza)
|
||||
if stanza.name == "handshake" then
|
||||
self:event("authentication-success");
|
||||
end
|
||||
end);
|
||||
end);
|
||||
|
||||
local function stream_ready()
|
||||
self:event("ready");
|
||||
end
|
||||
self:hook("authentication-success", stream_ready, -1);
|
||||
|
||||
-- Initialise connection
|
||||
self:connect(self.connect_host or self.host, self.connect_port or 5347);
|
||||
self:reopen();
|
||||
end
|
||||
|
||||
function stream:reopen()
|
||||
self:reset();
|
||||
self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
|
||||
xmlns = xmlns_component, version = "1.0" }):top_tag());
|
||||
end
|
||||
|
||||
function stream:close(reason)
|
||||
if not self.notopen then
|
||||
self:send("</stream:stream>");
|
||||
end
|
||||
local on_disconnect = self.conn.disconnect();
|
||||
self.conn:close();
|
||||
on_disconnect(conn, reason);
|
||||
end
|
||||
|
||||
function stream:send_iq(iq, callback)
|
||||
local id = self:new_id();
|
||||
self.tracked_iqs[id] = callback;
|
||||
iq.attr.id = id;
|
||||
self:send(iq);
|
||||
end
|
||||
|
||||
function stream:new_id()
|
||||
self.curr_id = self.curr_id + 1;
|
||||
return tostring(self.curr_id);
|
||||
end
|
||||
end)
|
||||
|
||||
-- Use LuaRocks if available
|
||||
pcall(require, "luarocks.require");
|
||||
|
||||
-- Load LuaSec if available
|
||||
pcall(require, "ssl");
|
||||
|
||||
local server = require "net.server";
|
||||
local events = require "util.events";
|
||||
local logger = require "util.logger";
|
||||
|
||||
module("verse", package.seeall);
|
||||
local verse = _M;
|
||||
_M.server = server;
|
||||
|
||||
local stream = {};
|
||||
stream.__index = stream;
|
||||
stream_mt = stream;
|
||||
|
||||
verse.plugins = {};
|
||||
|
||||
local max_id = 0;
|
||||
|
||||
function verse.new(logger, base)
|
||||
local t = setmetatable(base or {}, stream);
|
||||
max_id = max_id + 1;
|
||||
t.id = tostring(max_id);
|
||||
t.logger = logger or verse.new_logger("stream"..t.id);
|
||||
t.events = events.new();
|
||||
t.plugins = {};
|
||||
t.verse = verse;
|
||||
return t;
|
||||
end
|
||||
|
||||
verse.add_task = require "util.timer".add_task;
|
||||
|
||||
verse.logger = logger.init; -- COMPAT: Deprecated
|
||||
verse.new_logger = logger.init;
|
||||
verse.log = verse.logger("verse");
|
||||
|
||||
local function format(format, ...)
|
||||
local n, arg, maxn = 0, { ... }, select('#', ...);
|
||||
return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
|
||||
end
|
||||
|
||||
function verse.set_log_handler(log_handler, levels)
|
||||
levels = levels or { "debug", "info", "warn", "error" };
|
||||
logger.reset();
|
||||
local function _log_handler(name, level, message, ...)
|
||||
return log_handler(name, level, format(message, ...));
|
||||
end
|
||||
if log_handler then
|
||||
for i, level in ipairs(levels) do
|
||||
logger.add_level_sink(level, _log_handler);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _default_log_handler(name, level, message)
|
||||
return io.stderr:write(name, "\t", level, "\t", message, "\n");
|
||||
end
|
||||
verse.set_log_handler(_default_log_handler, { "error" });
|
||||
|
||||
local function error_handler(err)
|
||||
verse.log("error", "Error: %s", err);
|
||||
verse.log("error", "Traceback: %s", debug.traceback());
|
||||
end
|
||||
|
||||
function verse.set_error_handler(new_error_handler)
|
||||
error_handler = new_error_handler;
|
||||
end
|
||||
|
||||
function verse.loop()
|
||||
return xpcall(server.loop, error_handler);
|
||||
end
|
||||
|
||||
function verse.step()
|
||||
return xpcall(server.step, error_handler);
|
||||
end
|
||||
|
||||
function verse.quit()
|
||||
return server.setquitting(true);
|
||||
end
|
||||
|
||||
function stream:connect(connect_host, connect_port)
|
||||
connect_host = connect_host or "localhost";
|
||||
connect_port = tonumber(connect_port) or 5222;
|
||||
|
||||
-- Create and initiate connection
|
||||
local conn = socket.tcp()
|
||||
conn:settimeout(0);
|
||||
local success, err = conn:connect(connect_host, connect_port);
|
||||
|
||||
if not success and err ~= "timeout" then
|
||||
self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
|
||||
return self:event("disconnected", { reason = err }) or false, err;
|
||||
end
|
||||
|
||||
local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
|
||||
if not conn then
|
||||
self:warn("connection initialisation failed: %s", err);
|
||||
return self:event("disconnected", { reason = err }) or false, err;
|
||||
end
|
||||
|
||||
self.conn = conn;
|
||||
self.send = function (stream, data)
|
||||
self:event("outgoing", data);
|
||||
data = tostring(data);
|
||||
self:event("outgoing-raw", data);
|
||||
return conn:write(data);
|
||||
end;
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream:close()
|
||||
if not self.conn then
|
||||
verse.log("error", "Attempt to close disconnected connection - possibly a bug");
|
||||
return;
|
||||
end
|
||||
local on_disconnect = self.conn.disconnect();
|
||||
self.conn:close();
|
||||
on_disconnect(conn, reason);
|
||||
end
|
||||
|
||||
-- Logging functions
|
||||
function stream:debug(...)
|
||||
return self.logger("debug", ...);
|
||||
end
|
||||
|
||||
function stream:warn(...)
|
||||
return self.logger("warn", ...);
|
||||
end
|
||||
|
||||
function stream:error(...)
|
||||
return self.logger("error", ...);
|
||||
end
|
||||
|
||||
-- Event handling
|
||||
function stream:event(name, ...)
|
||||
self:debug("Firing event: "..tostring(name));
|
||||
return self.events.fire_event(name, ...);
|
||||
end
|
||||
|
||||
function stream:hook(name, ...)
|
||||
return self.events.add_handler(name, ...);
|
||||
end
|
||||
|
||||
function stream:unhook(name, handler)
|
||||
return self.events.remove_handler(name, handler);
|
||||
end
|
||||
|
||||
function verse.eventable(object)
|
||||
object.events = events.new();
|
||||
object.hook, object.unhook = stream.hook, stream.unhook;
|
||||
local fire_event = object.events.fire_event;
|
||||
function object:event(name, ...)
|
||||
return fire_event(name, ...);
|
||||
end
|
||||
return object;
|
||||
end
|
||||
|
||||
function stream:add_plugin(name)
|
||||
if self.plugins[name] then return true; end
|
||||
if require("verse.plugins."..name) then
|
||||
local ok, err = verse.plugins[name](self);
|
||||
if ok ~= false then
|
||||
self:debug("Loaded %s plugin", name);
|
||||
self.plugins[name] = true;
|
||||
else
|
||||
self:warn("Failed to load %s plugin: %s", name, err);
|
||||
end
|
||||
end
|
||||
return self;
|
||||
end
|
||||
|
||||
-- Listener factory
|
||||
function new_listener(stream)
|
||||
local conn_listener = {};
|
||||
|
||||
function conn_listener.onconnect(conn)
|
||||
stream.connected = true;
|
||||
stream:event("connected");
|
||||
end
|
||||
|
||||
function conn_listener.onincoming(conn, data)
|
||||
stream:event("incoming-raw", data);
|
||||
end
|
||||
|
||||
function conn_listener.ondisconnect(conn, err)
|
||||
stream.connected = false;
|
||||
stream:event("disconnected", { reason = err });
|
||||
end
|
||||
|
||||
function conn_listener.ondrain(conn)
|
||||
stream:event("drained");
|
||||
end
|
||||
|
||||
function conn_listener.onstatus(conn, new_status)
|
||||
stream:event("status", new_status);
|
||||
end
|
||||
|
||||
return conn_listener;
|
||||
end
|
||||
|
||||
return verse;
|
||||
|
BIN
verse/.hg/00changelog.i
Normal file
BIN
verse/.hg/00changelog.i
Normal file
Binary file not shown.
1
verse/.hg/branch
Normal file
1
verse/.hg/branch
Normal file
@ -0,0 +1 @@
|
||||
default
|
2
verse/.hg/cache/branchheads-served
vendored
Normal file
2
verse/.hg/cache/branchheads-served
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 368
|
||||
154c2f04d73b0a3ba1d4e0b12295c804f0fc3927 default
|
BIN
verse/.hg/dirstate
Normal file
BIN
verse/.hg/dirstate
Normal file
Binary file not shown.
2
verse/.hg/hgrc
Normal file
2
verse/.hg/hgrc
Normal file
@ -0,0 +1,2 @@
|
||||
[paths]
|
||||
default = http://code.matthewwild.co.uk/verse
|
4
verse/.hg/requires
Normal file
4
verse/.hg/requires
Normal file
@ -0,0 +1,4 @@
|
||||
dotencode
|
||||
fncache
|
||||
revlogv1
|
||||
store
|
BIN
verse/.hg/store/00changelog.i
Normal file
BIN
verse/.hg/store/00changelog.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/00manifest.i
Normal file
BIN
verse/.hg/store/00manifest.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/_l_i_c_e_n_s_e.i
Normal file
BIN
verse/.hg/store/data/_l_i_c_e_n_s_e.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/bosh.lua.i
Normal file
BIN
verse/.hg/store/data/bosh.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/client.lua.i
Normal file
BIN
verse/.hg/store/data/client.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/component.lua.i
Normal file
BIN
verse/.hg/store/data/component.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__adhoc.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__adhoc.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__bosh.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__bosh.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__carbons.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__carbons.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__component.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__component.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__jingle.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__jingle.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__jingle__send.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__jingle__send.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__pep.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__pep.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/doc/example__pubsub.lua.i
Normal file
BIN
verse/.hg/store/data/doc/example__pubsub.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/init.lua.i
Normal file
BIN
verse/.hg/store/data/init.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/adhoc.lib.lua.i
Normal file
BIN
verse/.hg/store/data/libs/adhoc.lib.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/bit.lua.i
Normal file
BIN
verse/.hg/store/data/libs/bit.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/encodings.lua.i
Normal file
BIN
verse/.hg/store/data/libs/encodings.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/hashes.lua.i
Normal file
BIN
verse/.hg/store/data/libs/hashes.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/logger.lua.i
Normal file
BIN
verse/.hg/store/data/libs/logger.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/libs/xstanza.lua.i
Normal file
BIN
verse/.hg/store/data/libs/xstanza.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/adhoc.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/adhoc.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/archive.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/archive.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/bind.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/bind.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/blocking.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/blocking.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/carbons.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/carbons.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/compression.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/compression.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/disco.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/disco.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/groupchat.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/groupchat.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/jingle.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/jingle.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/jingle__ft.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/jingle__ft.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/jingle__ibb.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/jingle__ibb.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/jingle__s5b.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/jingle__s5b.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/keepalive.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/keepalive.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/legacy.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/legacy.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/pep.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/pep.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/ping.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/ping.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/presence.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/presence.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/private.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/private.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/proxy65.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/proxy65.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/pubsub.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/pubsub.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/receipts.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/receipts.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/register.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/register.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/roster.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/roster.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/sasl.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/sasl.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/session.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/session.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/smacks.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/smacks.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/tls.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/tls.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/uptime.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/uptime.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/vcard.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/vcard.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/vcard__update.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/vcard__update.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/plugins/version.lua.i
Normal file
BIN
verse/.hg/store/data/plugins/version.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/squishy.i
Normal file
BIN
verse/.hg/store/data/squishy.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/dataforms.lua.i
Normal file
BIN
verse/.hg/store/data/util/dataforms.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/sasl/anonymous.lua.i
Normal file
BIN
verse/.hg/store/data/util/sasl/anonymous.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/sasl/plain.lua.i
Normal file
BIN
verse/.hg/store/data/util/sasl/plain.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/sasl/scram.lua.i
Normal file
BIN
verse/.hg/store/data/util/sasl/scram.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/sha1.lua.i
Normal file
BIN
verse/.hg/store/data/util/sha1.lua.i
Normal file
Binary file not shown.
BIN
verse/.hg/store/data/util/vcard.lua.i
Normal file
BIN
verse/.hg/store/data/util/vcard.lua.i
Normal file
Binary file not shown.
58
verse/.hg/store/fncache
Normal file
58
verse/.hg/store/fncache
Normal file
@ -0,0 +1,58 @@
|
||||
data/client.lua.i
|
||||
data/util/sasl/plain.lua.i
|
||||
data/plugins/private.lua.i
|
||||
data/plugins/version.lua.i
|
||||
data/util/sasl/scram.lua.i
|
||||
data/plugins/legacy.lua.i
|
||||
data/plugins/jingle_s5b.lua.i
|
||||
data/bosh.lua.i
|
||||
data/doc/example_adhoc.lua.i
|
||||
data/plugins/roster.lua.i
|
||||
data/plugins/jingle_ft.lua.i
|
||||
data/doc/example_bosh.lua.i
|
||||
data/doc/example_component.lua.i
|
||||
data/plugins/vcard_update.lua.i
|
||||
data/plugins/vcard.lua.i
|
||||
data/plugins/ping.lua.i
|
||||
data/plugins/session.lua.i
|
||||
data/plugins/adhoc.lua.i
|
||||
data/libs/logger.lua.i
|
||||
data/plugins/uptime.lua.i
|
||||
data/plugins/pubsub.lua.i
|
||||
data/libs/xstanza.lua.i
|
||||
data/doc/example_jingle_send.lua.i
|
||||
data/plugins/jingle.lua.i
|
||||
data/plugins/smacks.lua.i
|
||||
data/doc/example_jingle.lua.i
|
||||
data/plugins/register.lua.i
|
||||
data/plugins/carbons.lua.i
|
||||
data/plugins/jingle_ibb.lua.i
|
||||
data/init.lua.i
|
||||
data/plugins/presence.lua.i
|
||||
data/plugins/blocking.lua.i
|
||||
data/plugins/pep.lua.i
|
||||
data/libs/hashes.lua.i
|
||||
data/squishy.i
|
||||
data/plugins/bind.lua.i
|
||||
data/plugins/tls.lua.i
|
||||
data/doc/example_pep.lua.i
|
||||
data/util/sha1.lua.i
|
||||
data/doc/example.lua.i
|
||||
data/libs/bit.lua.i
|
||||
data/doc/example_pubsub.lua.i
|
||||
data/doc/example_carbons.lua.i
|
||||
data/plugins/receipts.lua.i
|
||||
data/libs/adhoc.lib.lua.i
|
||||
data/LICENSE.i
|
||||
data/plugins/disco.lua.i
|
||||
data/plugins/archive.lua.i
|
||||
data/util/sasl/anonymous.lua.i
|
||||
data/component.lua.i
|
||||
data/plugins/sasl.lua.i
|
||||
data/plugins/groupchat.lua.i
|
||||
data/util/vcard.lua.i
|
||||
data/plugins/compression.lua.i
|
||||
data/plugins/proxy65.lua.i
|
||||
data/util/dataforms.lua.i
|
||||
data/libs/encodings.lua.i
|
||||
data/plugins/keepalive.lua.i
|
0
verse/.hg/store/phaseroots
Normal file
0
verse/.hg/store/phaseroots
Normal file
BIN
verse/.hg/store/undo
Normal file
BIN
verse/.hg/store/undo
Normal file
Binary file not shown.
0
verse/.hg/store/undo.phaseroots
Normal file
0
verse/.hg/store/undo.phaseroots
Normal file
0
verse/.hg/undo.bookmarks
Normal file
0
verse/.hg/undo.bookmarks
Normal file
1
verse/.hg/undo.branch
Normal file
1
verse/.hg/undo.branch
Normal file
@ -0,0 +1 @@
|
||||
default
|
3
verse/.hg/undo.desc
Normal file
3
verse/.hg/undo.desc
Normal file
@ -0,0 +1,3 @@
|
||||
0
|
||||
pull
|
||||
http://code.matthewwild.co.uk/verse
|
0
verse/.hg/undo.dirstate
Normal file
0
verse/.hg/undo.dirstate
Normal file
19
verse/LICENSE
Normal file
19
verse/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2009-2010 Matthew Wild
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
209
verse/bosh.lua
Normal file
209
verse/bosh.lua
Normal file
@ -0,0 +1,209 @@
|
||||
|
||||
local new_xmpp_stream = require "util.xmppstream".new;
|
||||
local st = require "util.stanza";
|
||||
require "net.httpclient_listener"; -- Required for net.http to work
|
||||
local http = require "net.http";
|
||||
|
||||
local stream_mt = setmetatable({}, { __index = verse.stream_mt });
|
||||
stream_mt.__index = stream_mt;
|
||||
|
||||
local xmlns_stream = "http://etherx.jabber.org/streams";
|
||||
local xmlns_bosh = "http://jabber.org/protocol/httpbind";
|
||||
|
||||
local reconnect_timeout = 5;
|
||||
|
||||
function verse.new_bosh(logger, url)
|
||||
local stream = {
|
||||
bosh_conn_pool = {};
|
||||
bosh_waiting_requests = {};
|
||||
bosh_rid = math.random(1,999999);
|
||||
bosh_outgoing_buffer = {};
|
||||
bosh_url = url;
|
||||
conn = {};
|
||||
};
|
||||
function stream:reopen()
|
||||
self.bosh_need_restart = true;
|
||||
self:flush();
|
||||
end
|
||||
local conn = verse.new(logger, stream);
|
||||
return setmetatable(conn, stream_mt);
|
||||
end
|
||||
|
||||
function stream_mt:connect()
|
||||
self:_send_session_request();
|
||||
end
|
||||
|
||||
function stream_mt:send(data)
|
||||
self:debug("Putting into BOSH send buffer: %s", tostring(data));
|
||||
self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1] = st.clone(data);
|
||||
self:flush(); --TODO: Optimize by doing this on next tick (give a chance for data to buffer)
|
||||
end
|
||||
|
||||
function stream_mt:flush()
|
||||
if self.connected
|
||||
and #self.bosh_waiting_requests < self.bosh_max_requests
|
||||
and (#self.bosh_waiting_requests == 0
|
||||
or #self.bosh_outgoing_buffer > 0
|
||||
or self.bosh_need_restart) then
|
||||
self:debug("Flushing...");
|
||||
local payload = self:_make_body();
|
||||
local buffer = self.bosh_outgoing_buffer;
|
||||
for i, stanza in ipairs(buffer) do
|
||||
payload:add_child(stanza);
|
||||
buffer[i] = nil;
|
||||
end
|
||||
self:_make_request(payload);
|
||||
else
|
||||
self:debug("Decided not to flush.");
|
||||
end
|
||||
end
|
||||
|
||||
function stream_mt:_make_request(payload)
|
||||
local request, err = http.request(self.bosh_url, { body = tostring(payload) }, function (response, code, request)
|
||||
if code ~= 0 then
|
||||
self.inactive_since = nil;
|
||||
return self:_handle_response(response, code, request);
|
||||
end
|
||||
|
||||
-- Connection issues, we need to retry this request
|
||||
local time = os.time();
|
||||
if not self.inactive_since then
|
||||
self.inactive_since = time; -- So we know when it is time to give up
|
||||
elseif time - self.inactive_since > self.bosh_max_inactivity then
|
||||
return self:_disconnected();
|
||||
else
|
||||
self:debug("%d seconds left to reconnect, retrying in %d seconds...",
|
||||
self.bosh_max_inactivity - (time - self.inactive_since), reconnect_timeout);
|
||||
end
|
||||
|
||||
-- Set up reconnect timer
|
||||
timer.add_task(reconnect_timeout, function ()
|
||||
self:debug("Retrying request...");
|
||||
-- Remove old request
|
||||
for i, waiting_request in ipairs(self.bosh_waiting_requests) do
|
||||
if waiting_request == request then
|
||||
table.remove(self.bosh_waiting_requests, i);
|
||||
break;
|
||||
end
|
||||
end
|
||||
self:_make_request(payload);
|
||||
end);
|
||||
end);
|
||||
if request then
|
||||
table.insert(self.bosh_waiting_requests, request);
|
||||
else
|
||||
self:warn("Request failed instantly: %s", err);
|
||||
end
|
||||
end
|
||||
|
||||
function stream_mt:_disconnected()
|
||||
self.connected = nil;
|
||||
self:event("disconnected");
|
||||
end
|
||||
|
||||
function stream_mt:_send_session_request()
|
||||
local body = self:_make_body();
|
||||
|
||||
-- XEP-0124
|
||||
body.attr.hold = "1";
|
||||
body.attr.wait = "60";
|
||||
body.attr["xml:lang"] = "en";
|
||||
body.attr.ver = "1.6";
|
||||
|
||||
-- XEP-0206
|
||||
body.attr.from = self.jid;
|
||||
body.attr.to = self.host;
|
||||
body.attr.secure = 'true';
|
||||
|
||||
http.request(self.bosh_url, { body = tostring(body) }, function (response, code)
|
||||
if code == 0 then
|
||||
-- Failed to connect
|
||||
return self:_disconnected();
|
||||
end
|
||||
-- Handle session creation response
|
||||
local payload = self:_parse_response(response)
|
||||
if not payload then
|
||||
self:warn("Invalid session creation response");
|
||||
self:_disconnected();
|
||||
return;
|
||||
end
|
||||
self.bosh_sid = payload.attr.sid; -- Session id
|
||||
self.bosh_wait = tonumber(payload.attr.wait); -- How long the server may hold connections for
|
||||
self.bosh_hold = tonumber(payload.attr.hold); -- How many connections the server may hold
|
||||
self.bosh_max_inactivity = tonumber(payload.attr.inactivity); -- Max amount of time with no connections
|
||||
self.bosh_max_requests = tonumber(payload.attr.requests) or self.bosh_hold; -- Max simultaneous requests we can make
|
||||
self.connected = true;
|
||||
self:event("connected");
|
||||
self:_handle_response_payload(payload);
|
||||
end);
|
||||
end
|
||||
|
||||
function stream_mt:_handle_response(response, code, request)
|
||||
if self.bosh_waiting_requests[1] ~= request then
|
||||
self:warn("Server replied to request that wasn't the oldest");
|
||||
for i, waiting_request in ipairs(self.bosh_waiting_requests) do
|
||||
if waiting_request == request then
|
||||
self.bosh_waiting_requests[i] = nil;
|
||||
break;
|
||||
end
|
||||
end
|
||||
else
|
||||
table.remove(self.bosh_waiting_requests, 1);
|
||||
end
|
||||
local payload = self:_parse_response(response);
|
||||
if payload then
|
||||
self:_handle_response_payload(payload);
|
||||
end
|
||||
self:flush();
|
||||
end
|
||||
|
||||
function stream_mt:_handle_response_payload(payload)
|
||||
local stanzas = payload.tags;
|
||||
for i = 1, #stanzas do
|
||||
local stanza = stanzas[i];
|
||||
if stanza.attr.xmlns == xmlns_stream then
|
||||
self:event("stream-"..stanza.name, stanza);
|
||||
elseif stanza.attr.xmlns then
|
||||
self:event("stream/"..stanza.attr.xmlns, stanza);
|
||||
else
|
||||
self:event("stanza", stanza);
|
||||
end
|
||||
end
|
||||
if payload.attr.type == "terminate" then
|
||||
self:_disconnected({reason = payload.attr.condition});
|
||||
end
|
||||
end
|
||||
|
||||
local stream_callbacks = {
|
||||
stream_ns = "http://jabber.org/protocol/httpbind", stream_tag = "body",
|
||||
default_ns = "jabber:client",
|
||||
streamopened = function (session, attr) session.notopen = nil; session.payload = verse.stanza("body", attr); return true; end;
|
||||
handlestanza = function (session, stanza) session.payload:add_child(stanza); end;
|
||||
};
|
||||
function stream_mt:_parse_response(response)
|
||||
self:debug("Parsing response: %s", response);
|
||||
if response == nil then
|
||||
self:debug("%s", debug.traceback());
|
||||
self:_disconnected();
|
||||
return;
|
||||
end
|
||||
local session = { notopen = true, stream = self };
|
||||
local stream = new_xmpp_stream(session, stream_callbacks);
|
||||
stream:feed(response);
|
||||
return session.payload;
|
||||
end
|
||||
|
||||
function stream_mt:_make_body()
|
||||
self.bosh_rid = self.bosh_rid + 1;
|
||||
local body = verse.stanza("body", {
|
||||
xmlns = xmlns_bosh;
|
||||
content = "text/xml; charset=utf-8";
|
||||
sid = self.bosh_sid;
|
||||
rid = self.bosh_rid;
|
||||
});
|
||||
if self.bosh_need_restart then
|
||||
self.bosh_need_restart = nil;
|
||||
body.attr.restart = 'true';
|
||||
end
|
||||
return body;
|
||||
end
|
219
verse/client.lua
Normal file
219
verse/client.lua
Normal file
@ -0,0 +1,219 @@
|
||||
local verse = require "verse";
|
||||
local stream = verse.stream_mt;
|
||||
|
||||
local jid_split = require "util.jid".split;
|
||||
local adns = require "net.adns";
|
||||
local lxp = require "lxp";
|
||||
local st = require "util.stanza";
|
||||
|
||||
-- Shortcuts to save having to load util.stanza
|
||||
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
|
||||
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
|
||||
|
||||
local new_xmpp_stream = require "util.xmppstream".new;
|
||||
|
||||
local xmlns_stream = "http://etherx.jabber.org/streams";
|
||||
|
||||
local function compare_srv_priorities(a,b)
|
||||
return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
|
||||
end
|
||||
|
||||
local stream_callbacks = {
|
||||
stream_ns = xmlns_stream,
|
||||
stream_tag = "stream",
|
||||
default_ns = "jabber:client" };
|
||||
|
||||
function stream_callbacks.streamopened(stream, attr)
|
||||
stream.stream_id = attr.id;
|
||||
if not stream:event("opened", attr) then
|
||||
stream.notopen = nil;
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream_callbacks.streamclosed(stream)
|
||||
stream.notopen = true;
|
||||
if not stream.closed then
|
||||
stream:send("</stream:stream>");
|
||||
stream.closed = true;
|
||||
end
|
||||
stream:event("closed");
|
||||
return stream:close("stream closed")
|
||||
end
|
||||
|
||||
function stream_callbacks.handlestanza(stream, stanza)
|
||||
if stanza.attr.xmlns == xmlns_stream then
|
||||
return stream:event("stream-"..stanza.name, stanza);
|
||||
elseif stanza.attr.xmlns then
|
||||
return stream:event("stream/"..stanza.attr.xmlns, stanza);
|
||||
end
|
||||
|
||||
return stream:event("stanza", stanza);
|
||||
end
|
||||
|
||||
function stream_callbacks.error(stream, e, stanza)
|
||||
if stream:event(e, stanza) == nil then
|
||||
if stanza then
|
||||
local err = stanza:get_child(nil, "urn:ietf:params:xml:ns:xmpp-streams");
|
||||
local text = stanza:get_child_text("text", "urn:ietf:params:xml:ns:xmpp-streams");
|
||||
error(err.name..(text and ": "..text or ""));
|
||||
else
|
||||
error(stanza and stanza.name or e or "unknown-error");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function stream:reset()
|
||||
if self.stream then
|
||||
self.stream:reset();
|
||||
else
|
||||
self.stream = new_xmpp_stream(self, stream_callbacks);
|
||||
end
|
||||
self.notopen = true;
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream:connect_client(jid, pass)
|
||||
self.jid, self.password = jid, pass;
|
||||
self.username, self.host, self.resource = jid_split(jid);
|
||||
|
||||
-- Required XMPP features
|
||||
self:add_plugin("tls");
|
||||
self:add_plugin("sasl");
|
||||
self:add_plugin("bind");
|
||||
self:add_plugin("session");
|
||||
|
||||
function self.data(conn, data)
|
||||
local ok, err = self.stream:feed(data);
|
||||
if ok then return; end
|
||||
self:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
|
||||
self:close("xml-not-well-formed");
|
||||
end
|
||||
|
||||
self:hook("connected", function () self:reopen(); end);
|
||||
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
|
||||
|
||||
self.curr_id = 0;
|
||||
|
||||
self.tracked_iqs = {};
|
||||
self:hook("stanza", function (stanza)
|
||||
local id, type = stanza.attr.id, stanza.attr.type;
|
||||
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
|
||||
self.tracked_iqs[id](stanza);
|
||||
self.tracked_iqs[id] = nil;
|
||||
return true;
|
||||
end
|
||||
end);
|
||||
|
||||
self:hook("stanza", function (stanza)
|
||||
local ret;
|
||||
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
|
||||
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
|
||||
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
|
||||
if xmlns then
|
||||
ret = self:event("iq/"..xmlns, stanza);
|
||||
if not ret then
|
||||
ret = self:event("iq", stanza);
|
||||
end
|
||||
end
|
||||
if ret == nil then
|
||||
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
|
||||
return true;
|
||||
end
|
||||
else
|
||||
ret = self:event(stanza.name, stanza);
|
||||
end
|
||||
end
|
||||
return ret;
|
||||
end, -1);
|
||||
|
||||
self:hook("outgoing", function (data)
|
||||
if data.name then
|
||||
self:event("stanza-out", data);
|
||||
end
|
||||
end);
|
||||
|
||||
self:hook("stanza-out", function (stanza)
|
||||
if not stanza.attr.xmlns then
|
||||
self:event(stanza.name.."-out", stanza);
|
||||
end
|
||||
end);
|
||||
|
||||
local function stream_ready()
|
||||
self:event("ready");
|
||||
end
|
||||
self:hook("session-success", stream_ready, -1)
|
||||
self:hook("bind-success", stream_ready, -1);
|
||||
|
||||
local _base_close = self.close;
|
||||
function self:close(reason)
|
||||
self.close = _base_close;
|
||||
if not self.closed then
|
||||
self:send("</stream:stream>");
|
||||
self.closed = true;
|
||||
else
|
||||
return self:close(reason);
|
||||
end
|
||||
end
|
||||
|
||||
local function start_connect()
|
||||
-- Initialise connection
|
||||
self:connect(self.connect_host or self.host, self.connect_port or 5222);
|
||||
end
|
||||
|
||||
if not (self.connect_host or self.connect_port) then
|
||||
-- Look up SRV records
|
||||
adns.lookup(function (answer)
|
||||
if answer then
|
||||
local srv_hosts = {};
|
||||
self.srv_hosts = srv_hosts;
|
||||
for _, record in ipairs(answer) do
|
||||
table.insert(srv_hosts, record.srv);
|
||||
end
|
||||
table.sort(srv_hosts, compare_srv_priorities);
|
||||
|
||||
local srv_choice = srv_hosts[1];
|
||||
self.srv_choice = 1;
|
||||
if srv_choice then
|
||||
self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
|
||||
self:debug("Best record found, will connect to %s:%d", self.connect_host or self.host, self.connect_port or 5222);
|
||||
end
|
||||
|
||||
self:hook("disconnected", function ()
|
||||
if self.srv_hosts and self.srv_choice < #self.srv_hosts then
|
||||
self.srv_choice = self.srv_choice + 1;
|
||||
local srv_choice = srv_hosts[self.srv_choice];
|
||||
self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
|
||||
start_connect();
|
||||
return true;
|
||||
end
|
||||
end, 1000);
|
||||
|
||||
self:hook("connected", function ()
|
||||
self.srv_hosts = nil;
|
||||
end, 1000);
|
||||
end
|
||||
start_connect();
|
||||
end, "_xmpp-client._tcp."..(self.host)..".", "SRV");
|
||||
else
|
||||
start_connect();
|
||||
end
|
||||
end
|
||||
|
||||
function stream:reopen()
|
||||
self:reset();
|
||||
self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
|
||||
xmlns = "jabber:client", version = "1.0" }):top_tag());
|
||||
end
|
||||
|
||||
function stream:send_iq(iq, callback)
|
||||
local id = self:new_id();
|
||||
self.tracked_iqs[id] = callback;
|
||||
iq.attr.id = id;
|
||||
self:send(iq);
|
||||
end
|
||||
|
||||
function stream:new_id()
|
||||
self.curr_id = self.curr_id + 1;
|
||||
return tostring(self.curr_id);
|
||||
end
|
149
verse/component.lua
Normal file
149
verse/component.lua
Normal file
@ -0,0 +1,149 @@
|
||||
local verse = require "verse";
|
||||
local stream = verse.stream_mt;
|
||||
|
||||
local jid_split = require "util.jid".split;
|
||||
local lxp = require "lxp";
|
||||
local st = require "util.stanza";
|
||||
local sha1 = require "util.sha1".sha1;
|
||||
|
||||
-- Shortcuts to save having to load util.stanza
|
||||
verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply =
|
||||
st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply;
|
||||
|
||||
local new_xmpp_stream = require "util.xmppstream".new;
|
||||
|
||||
local xmlns_stream = "http://etherx.jabber.org/streams";
|
||||
local xmlns_component = "jabber:component:accept";
|
||||
|
||||
local stream_callbacks = {
|
||||
stream_ns = xmlns_stream,
|
||||
stream_tag = "stream",
|
||||
default_ns = xmlns_component };
|
||||
|
||||
function stream_callbacks.streamopened(stream, attr)
|
||||
stream.stream_id = attr.id;
|
||||
if not stream:event("opened", attr) then
|
||||
stream.notopen = nil;
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream_callbacks.streamclosed(stream)
|
||||
return stream:event("closed");
|
||||
end
|
||||
|
||||
function stream_callbacks.handlestanza(stream, stanza)
|
||||
if stanza.attr.xmlns == xmlns_stream then
|
||||
return stream:event("stream-"..stanza.name, stanza);
|
||||
elseif stanza.attr.xmlns or stanza.name == "handshake" then
|
||||
return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza);
|
||||
end
|
||||
|
||||
return stream:event("stanza", stanza);
|
||||
end
|
||||
|
||||
function stream:reset()
|
||||
if self.stream then
|
||||
self.stream:reset();
|
||||
else
|
||||
self.stream = new_xmpp_stream(self, stream_callbacks);
|
||||
end
|
||||
self.notopen = true;
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream:connect_component(jid, pass)
|
||||
self.jid, self.password = jid, pass;
|
||||
self.username, self.host, self.resource = jid_split(jid);
|
||||
|
||||
function self.data(conn, data)
|
||||
local ok, err = self.stream:feed(data);
|
||||
if ok then return; end
|
||||
stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
|
||||
stream:close("xml-not-well-formed");
|
||||
end
|
||||
|
||||
self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
|
||||
|
||||
self.curr_id = 0;
|
||||
|
||||
self.tracked_iqs = {};
|
||||
self:hook("stanza", function (stanza)
|
||||
local id, type = stanza.attr.id, stanza.attr.type;
|
||||
if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
|
||||
self.tracked_iqs[id](stanza);
|
||||
self.tracked_iqs[id] = nil;
|
||||
return true;
|
||||
end
|
||||
end);
|
||||
|
||||
self:hook("stanza", function (stanza)
|
||||
local ret;
|
||||
if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
|
||||
if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
|
||||
local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
|
||||
if xmlns then
|
||||
ret = self:event("iq/"..xmlns, stanza);
|
||||
if not ret then
|
||||
ret = self:event("iq", stanza);
|
||||
end
|
||||
end
|
||||
if ret == nil then
|
||||
self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
|
||||
return true;
|
||||
end
|
||||
else
|
||||
ret = self:event(stanza.name, stanza);
|
||||
end
|
||||
end
|
||||
return ret;
|
||||
end, -1);
|
||||
|
||||
self:hook("opened", function (attr)
|
||||
print(self.jid, self.stream_id, attr.id);
|
||||
local token = sha1(self.stream_id..pass, true);
|
||||
|
||||
self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token));
|
||||
self:hook("stream/"..xmlns_component, function (stanza)
|
||||
if stanza.name == "handshake" then
|
||||
self:event("authentication-success");
|
||||
end
|
||||
end);
|
||||
end);
|
||||
|
||||
local function stream_ready()
|
||||
self:event("ready");
|
||||
end
|
||||
self:hook("authentication-success", stream_ready, -1);
|
||||
|
||||
-- Initialise connection
|
||||
self:connect(self.connect_host or self.host, self.connect_port or 5347);
|
||||
self:reopen();
|
||||
end
|
||||
|
||||
function stream:reopen()
|
||||
self:reset();
|
||||
self:send(st.stanza("stream:stream", { to = self.jid, ["xmlns:stream"]='http://etherx.jabber.org/streams',
|
||||
xmlns = xmlns_component, version = "1.0" }):top_tag());
|
||||
end
|
||||
|
||||
function stream:close(reason)
|
||||
if not self.notopen then
|
||||
self:send("</stream:stream>");
|
||||
end
|
||||
local on_disconnect = self.conn.disconnect();
|
||||
self.conn:close();
|
||||
on_disconnect(conn, reason);
|
||||
end
|
||||
|
||||
function stream:send_iq(iq, callback)
|
||||
local id = self:new_id();
|
||||
self.tracked_iqs[id] = callback;
|
||||
iq.attr.id = id;
|
||||
self:send(iq);
|
||||
end
|
||||
|
||||
function stream:new_id()
|
||||
self.curr_id = self.curr_id + 1;
|
||||
return tostring(self.curr_id);
|
||||
end
|
39
verse/doc/example.lua
Normal file
39
verse/doc/example.lua
Normal file
@ -0,0 +1,39 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com", "secret";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none verse");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new();
|
||||
c:add_plugin("version");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
c.version:set{ name = "verse example client" };
|
||||
c:query_version(c.jid, function (v) print("I am using "..(v.name or "<unknown>")); end);
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
48
verse/doc/example_adhoc.lua
Normal file
48
verse/doc/example_adhoc.lua
Normal file
@ -0,0 +1,48 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com", "secret";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new(verse.logger());
|
||||
c:add_plugin("version");
|
||||
c:add_plugin("disco");
|
||||
c:add_plugin("adhoc");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
c.version:set{ name = "verse example client" };
|
||||
|
||||
local function random_handler()
|
||||
return { info = tostring(math.random(1,100)), status = "completed" };
|
||||
end
|
||||
|
||||
c:add_adhoc_command("Get random number", "random", random_handler);
|
||||
|
||||
c:send(verse.presence():add_child(c:caps()));
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
40
verse/doc/example_bosh.lua
Normal file
40
verse/doc/example_bosh.lua
Normal file
@ -0,0 +1,40 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com", "secret";
|
||||
local url = "http://example.com:80/http-bind";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none verse");
|
||||
|
||||
require "verse".init("client", "bosh");
|
||||
|
||||
c = verse.new_bosh(nil, url);
|
||||
c:add_plugin("version");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
c.version:set{ name = "Verse example BOSH client" };
|
||||
c:query_version(c.jid, function (v) print("I am using "..(v.name or "<unknown>")); end);
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
46
verse/doc/example_carbons.lua
Normal file
46
verse/doc/example_carbons.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local xmlns_carbons = "urn:xmpp:carbons:1";
|
||||
local xmlns_forward = "urn:xmpp:forward:0";
|
||||
|
||||
local function datetime(t) return os_date("!%Y-%m-%dT%H:%M:%SZ", t); end
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none verse");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new();--verse.logger());
|
||||
c:add_plugin "carbons"
|
||||
|
||||
c:hook("disconnected", verse.quit);
|
||||
local jid, password = unpack(arg);
|
||||
assert(jid and password, "You need to supply JID and password as arguments");
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () c:debug("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err)
|
||||
c:error("Failed to log in! Error: "..tostring(err.condition));
|
||||
c:close();
|
||||
end);
|
||||
|
||||
c:hook("carbon", function(carbon)
|
||||
local dir, ts, st = carbon.dir, carbon.timestamp, carbon.stanza;
|
||||
print("", datetime(ts), dir:upper());
|
||||
print(st);
|
||||
end);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
c:debug("Connected");
|
||||
c.carbons:enable(function(ok)
|
||||
if ok then
|
||||
c:debug("Carbons enabled")
|
||||
else
|
||||
c:error("Could not enable carbons, aborting");
|
||||
c:close();
|
||||
end
|
||||
end);
|
||||
end);
|
||||
|
||||
verse.loop()
|
43
verse/doc/example_component.lua
Normal file
43
verse/doc/example_component.lua
Normal file
@ -0,0 +1,43 @@
|
||||
local jid, password = "echo.localhost", "hellohello";
|
||||
|
||||
--os.execute("squish --minify-level=none verse");
|
||||
|
||||
require "verse".init("component");
|
||||
|
||||
c = verse.new(verse.logger())
|
||||
c:add_plugin("version");
|
||||
c:add_plugin("ping");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c.connect_host = "127.0.0.1"
|
||||
c:connect_component(jid, password);
|
||||
|
||||
-- Catch binding-success which is (currently) how you know when a stream is ready
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
c.version:set{ name = "verse example component" };
|
||||
end);
|
||||
|
||||
-- Echo, echo, echo, echo...
|
||||
c:hook("stanza", function (stanza)
|
||||
stanza.attr.from, stanza.attr.to = stanza.attr.to, stanza.attr.from;
|
||||
c:send(stanza);
|
||||
end)
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
56
verse/doc/example_jingle.lua
Normal file
56
verse/doc/example_jingle.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com/receiver", "secret";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new(verse.logger())
|
||||
c:add_plugin("version");
|
||||
c:add_plugin("disco");
|
||||
c:add_plugin("proxy65");
|
||||
c:add_plugin("jingle");
|
||||
c:add_plugin("jingle_ft");
|
||||
c:add_plugin("jingle_s5b");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end, 15);
|
||||
|
||||
-- This one prints all received data
|
||||
--c:hook("incoming-raw", function (...) print("<<", ...) end, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
|
||||
-- Handle incoming Jingle requests
|
||||
c:hook("jingle", function (jingle)
|
||||
if jingle.content.type == "file" then
|
||||
print("File offer from "..jingle.peer.."!");
|
||||
print("Filename: "..jingle.content.file.name.." Size: "..jingle.content.file.size);
|
||||
jingle:accept({ save_file = jingle.content.file.name..".received" });
|
||||
end
|
||||
end);
|
||||
|
||||
c:send(verse.presence()
|
||||
:tag("status"):text("Send me a file!"):up()
|
||||
:add_child(c:caps())
|
||||
);
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
52
verse/doc/example_jingle_send.lua
Normal file
52
verse/doc/example_jingle_send.lua
Normal file
@ -0,0 +1,52 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com/sender", "secret";
|
||||
local receiver = "user@example.com/receiver";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new(verse.logger())
|
||||
c:add_plugin("version");
|
||||
c:add_plugin("disco");
|
||||
c:add_plugin("proxy65");
|
||||
c:add_plugin("jingle");
|
||||
c:add_plugin("jingle_ft");
|
||||
c:add_plugin("jingle_s5b");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end, 15);
|
||||
|
||||
-- This one prints all received data
|
||||
--c:hook("incoming-raw", function (...) print("<<", ...) end, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
|
||||
c:send(verse.presence()
|
||||
:tag("status"):text("Let me send you a file!"):up()
|
||||
:add_child(c:caps())
|
||||
);
|
||||
|
||||
c:hook("proxy65/discovered-proxies", function ()
|
||||
print(c:send_file(receiver, "jingle.txt"));
|
||||
end);
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
56
verse/doc/example_pep.lua
Normal file
56
verse/doc/example_pep.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com", "secret";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new();
|
||||
c:add_plugin("version");
|
||||
c:add_plugin("pep");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
c.version:set{ name = "verse example client" };
|
||||
c:hook_pep("http://jabber.org/protocol/mood", function (event)
|
||||
print(event.from.." is "..event.item.tags[1].name);
|
||||
end);
|
||||
|
||||
c:hook_pep("http://jabber.org/protocol/tune", function (event)
|
||||
print(event.from.." is listening to "..event.item:get_child_text("title"));
|
||||
end);
|
||||
|
||||
c:send(verse.presence());
|
||||
|
||||
c:publish_pep(verse.stanza("tune", { xmlns = "http://jabber.org/protocol/tune" })
|
||||
:tag("title"):text("Beautiful Cedars"):up()
|
||||
:tag("artist"):text("The Spinners"):up()
|
||||
:tag("source"):text("Not Quite Folk"):up()
|
||||
:tag("track"):text("4"):up()
|
||||
);
|
||||
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
56
verse/doc/example_pubsub.lua
Normal file
56
verse/doc/example_pubsub.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- Change these:
|
||||
local jid, password = "user@example.com", "secret";
|
||||
|
||||
-- This line squishes verse each time you run,
|
||||
-- handy if you're hacking on Verse itself
|
||||
--os.execute("squish --minify-level=none");
|
||||
|
||||
require "verse".init("client");
|
||||
|
||||
c = verse.new();
|
||||
c:add_plugin("pubsub");
|
||||
|
||||
-- Add some hooks for debugging
|
||||
c:hook("opened", function () print("Stream opened!") end);
|
||||
c:hook("closed", function () print("Stream closed!") end);
|
||||
c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
|
||||
|
||||
-- This one prints all received data
|
||||
c:hook("incoming-raw", print, 1000);
|
||||
|
||||
-- Print a message after authentication
|
||||
c:hook("authentication-success", function () print("Logged in!"); end);
|
||||
c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
|
||||
|
||||
-- Print a message and exit when disconnected
|
||||
c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
|
||||
|
||||
-- Now, actually start the connection:
|
||||
c:connect_client(jid, password);
|
||||
|
||||
-- Catch the "ready" event to know when the stream is ready to use
|
||||
c:hook("ready", function ()
|
||||
print("Stream ready!");
|
||||
|
||||
-- Create a reference to a node
|
||||
local node = c.pubsub("pubsub.shakespeare.lit", "princely_musings");
|
||||
|
||||
-- Callback for when something is published to the node
|
||||
node:hook(function(event)
|
||||
print(event.item)
|
||||
end);
|
||||
node:subscribe() -- so we actually get the notifications that above callback would get
|
||||
|
||||
node:publish(
|
||||
nil, -- no id, so the service should give us one
|
||||
nil, -- no options (not supported at the time of this writing)
|
||||
verse.stanza("something", { xmlns = "http://example.com/pubsub-thingy" }) -- the actual payload, would turn up in event.item above
|
||||
:tag("foobar"),
|
||||
function(success) -- callback
|
||||
print("publish", success and "success" or "failure")
|
||||
end)
|
||||
end);
|
||||
|
||||
print("Starting loop...")
|
||||
verse.loop()
|
||||
|
250
verse/init.lua
Normal file
250
verse/init.lua
Normal file
@ -0,0 +1,250 @@
|
||||
|
||||
-- Use LuaRocks if available
|
||||
pcall(require, "luarocks.require");
|
||||
|
||||
local socket = require"socket";
|
||||
|
||||
-- Load LuaSec if available
|
||||
pcall(require, "ssl");
|
||||
|
||||
local server = require "net.server";
|
||||
local events = require "util.events";
|
||||
local logger = require "util.logger";
|
||||
|
||||
module("verse", package.seeall);
|
||||
local verse = _M;
|
||||
_M.server = server;
|
||||
|
||||
local stream = {};
|
||||
stream.__index = stream;
|
||||
stream_mt = stream;
|
||||
|
||||
verse.plugins = {};
|
||||
|
||||
function verse.init(...)
|
||||
for i=1,select("#", ...) do
|
||||
local ok = pcall(require, "verse."..select(i,...));
|
||||
if not ok then
|
||||
error("Verse connection module not found: verse."..select(i,...));
|
||||
end
|
||||
end
|
||||
return verse;
|
||||
end
|
||||
|
||||
|
||||
local max_id = 0;
|
||||
|
||||
function verse.new(logger, base)
|
||||
local t = setmetatable(base or {}, stream);
|
||||
max_id = max_id + 1;
|
||||
t.id = tostring(max_id);
|
||||
t.logger = logger or verse.new_logger("stream"..t.id);
|
||||
t.events = events.new();
|
||||
t.plugins = {};
|
||||
t.verse = verse;
|
||||
return t;
|
||||
end
|
||||
|
||||
verse.add_task = require "util.timer".add_task;
|
||||
|
||||
verse.logger = logger.init; -- COMPAT: Deprecated
|
||||
verse.new_logger = logger.init;
|
||||
verse.log = verse.logger("verse");
|
||||
|
||||
local function format(format, ...)
|
||||
local n, arg, maxn = 0, { ... }, select('#', ...);
|
||||
return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end));
|
||||
end
|
||||
|
||||
function verse.set_log_handler(log_handler, levels)
|
||||
levels = levels or { "debug", "info", "warn", "error" };
|
||||
logger.reset();
|
||||
if io.type(log_handler) == "file" then
|
||||
local f = log_handler;
|
||||
function log_handler(name, level, message)
|
||||
f:write(name, "\t", level, "\t", message, "\n");
|
||||
end
|
||||
end
|
||||
if log_handler then
|
||||
local function _log_handler(name, level, message, ...)
|
||||
return log_handler(name, level, format(message, ...));
|
||||
end
|
||||
for i, level in ipairs(levels) do
|
||||
logger.add_level_sink(level, _log_handler);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function _default_log_handler(name, level, message)
|
||||
return io.stderr:write(name, "\t", level, "\t", message, "\n");
|
||||
end
|
||||
verse.set_log_handler(_default_log_handler, { "error" });
|
||||
|
||||
local function error_handler(err)
|
||||
verse.log("error", "Error: %s", err);
|
||||
verse.log("error", "Traceback: %s", debug.traceback());
|
||||
end
|
||||
|
||||
function verse.set_error_handler(new_error_handler)
|
||||
error_handler = new_error_handler;
|
||||
end
|
||||
|
||||
function verse.loop()
|
||||
return xpcall(server.loop, error_handler);
|
||||
end
|
||||
|
||||
function verse.step()
|
||||
return xpcall(server.step, error_handler);
|
||||
end
|
||||
|
||||
function verse.quit()
|
||||
return server.setquitting(true);
|
||||
end
|
||||
|
||||
function stream:listen(host, port)
|
||||
host = host or "localhost";
|
||||
port = port or 0;
|
||||
local conn, err = server.addserver(host, port, new_listener(self, "server"), "*a");
|
||||
if conn then
|
||||
self:debug("Bound to %s:%s", host, port);
|
||||
self.server = conn;
|
||||
end
|
||||
return conn, err;
|
||||
end
|
||||
|
||||
function stream:connect(connect_host, connect_port)
|
||||
connect_host = connect_host or "localhost";
|
||||
connect_port = tonumber(connect_port) or 5222;
|
||||
|
||||
-- Create and initiate connection
|
||||
local conn = socket.tcp()
|
||||
conn:settimeout(0);
|
||||
local success, err = conn:connect(connect_host, connect_port);
|
||||
|
||||
if not success and err ~= "timeout" then
|
||||
self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err);
|
||||
return self:event("disconnected", { reason = err }) or false, err;
|
||||
end
|
||||
|
||||
local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a");
|
||||
if not conn then
|
||||
self:warn("connection initialisation failed: %s", err);
|
||||
return self:event("disconnected", { reason = err }) or false, err;
|
||||
end
|
||||
self:set_conn(conn);
|
||||
return true;
|
||||
end
|
||||
|
||||
function stream:set_conn(conn)
|
||||
self.conn = conn;
|
||||
self.send = function (stream, data)
|
||||
self:event("outgoing", data);
|
||||
data = tostring(data);
|
||||
self:event("outgoing-raw", data);
|
||||
return conn:write(data);
|
||||
end;
|
||||
end
|
||||
|
||||
function stream:close(reason)
|
||||
if not self.conn then
|
||||
verse.log("error", "Attempt to close disconnected connection - possibly a bug");
|
||||
return;
|
||||
end
|
||||
local on_disconnect = self.conn.disconnect();
|
||||
self.conn:close();
|
||||
on_disconnect(self.conn, reason);
|
||||
end
|
||||
|
||||
-- Logging functions
|
||||
function stream:debug(...)
|
||||
return self.logger("debug", ...);
|
||||
end
|
||||
|
||||
function stream:info(...)
|
||||
return self.logger("info", ...);
|
||||
end
|
||||
|
||||
function stream:warn(...)
|
||||
return self.logger("warn", ...);
|
||||
end
|
||||
|
||||
function stream:error(...)
|
||||
return self.logger("error", ...);
|
||||
end
|
||||
|
||||
-- Event handling
|
||||
function stream:event(name, ...)
|
||||
self:debug("Firing event: "..tostring(name));
|
||||
return self.events.fire_event(name, ...);
|
||||
end
|
||||
|
||||
function stream:hook(name, ...)
|
||||
return self.events.add_handler(name, ...);
|
||||
end
|
||||
|
||||
function stream:unhook(name, handler)
|
||||
return self.events.remove_handler(name, handler);
|
||||
end
|
||||
|
||||
function verse.eventable(object)
|
||||
object.events = events.new();
|
||||
object.hook, object.unhook = stream.hook, stream.unhook;
|
||||
local fire_event = object.events.fire_event;
|
||||
function object:event(name, ...)
|
||||
return fire_event(name, ...);
|
||||
end
|
||||
return object;
|
||||
end
|
||||
|
||||
function stream:add_plugin(name)
|
||||
if self.plugins[name] then return true; end
|
||||
if require("verse.plugins."..name) then
|
||||
local ok, err = verse.plugins[name](self);
|
||||
if ok ~= false then
|
||||
self:debug("Loaded %s plugin", name);
|
||||
self.plugins[name] = true;
|
||||
else
|
||||
self:warn("Failed to load %s plugin: %s", name, err);
|
||||
end
|
||||
end
|
||||
return self;
|
||||
end
|
||||
|
||||
-- Listener factory
|
||||
function new_listener(stream)
|
||||
local conn_listener = {};
|
||||
|
||||
function conn_listener.onconnect(conn)
|
||||
if stream.server then
|
||||
local client = verse.new();
|
||||
conn:setlistener(new_listener(client));
|
||||
client:set_conn(conn);
|
||||
stream:event("connected", { client = client });
|
||||
else
|
||||
stream.connected = true;
|
||||
stream:event("connected");
|
||||
end
|
||||
end
|
||||
|
||||
function conn_listener.onincoming(conn, data)
|
||||
stream:event("incoming-raw", data);
|
||||
end
|
||||
|
||||
function conn_listener.ondisconnect(conn, err)
|
||||
if conn ~= stream.conn then return end
|
||||
stream.connected = false;
|
||||
stream:event("disconnected", { reason = err });
|
||||
end
|
||||
|
||||
function conn_listener.ondrain(conn)
|
||||
stream:event("drained");
|
||||
end
|
||||
|
||||
function conn_listener.onstatus(conn, new_status)
|
||||
stream:event("status", new_status);
|
||||
end
|
||||
|
||||
return conn_listener;
|
||||
end
|
||||
|
||||
return verse;
|
85
verse/libs/adhoc.lib.lua
Normal file
85
verse/libs/adhoc.lib.lua
Normal file
@ -0,0 +1,85 @@
|
||||
-- Copyright (C) 2009-2010 Florian Zeitz
|
||||
--
|
||||
-- This file is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
--
|
||||
|
||||
local st, uuid = require "util.stanza", require "util.uuid";
|
||||
|
||||
local xmlns_cmd = "http://jabber.org/protocol/commands";
|
||||
|
||||
local states = {}
|
||||
|
||||
local _M = {};
|
||||
|
||||
function _cmdtag(desc, status, sessionid, action)
|
||||
local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status });
|
||||
if sessionid then cmd.attr.sessionid = sessionid; end
|
||||
if action then cmd.attr.action = action; end
|
||||
|
||||
return cmd;
|
||||
end
|
||||
|
||||
function _M.new(name, node, handler, permission)
|
||||
return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") };
|
||||
end
|
||||
|
||||
function _M.handle_cmd(command, origin, stanza)
|
||||
local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
|
||||
local dataIn = {};
|
||||
dataIn.to = stanza.attr.to;
|
||||
dataIn.from = stanza.attr.from;
|
||||
dataIn.action = stanza.tags[1].attr.action or "execute";
|
||||
dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data");
|
||||
|
||||
local data, state = command:handler(dataIn, states[sessionid]);
|
||||
states[sessionid] = state;
|
||||
local stanza = st.reply(stanza);
|
||||
if data.status == "completed" then
|
||||
states[sessionid] = nil;
|
||||
cmdtag = command:cmdtag("completed", sessionid);
|
||||
elseif data.status == "canceled" then
|
||||
states[sessionid] = nil;
|
||||
cmdtag = command:cmdtag("canceled", sessionid);
|
||||
elseif data.status == "error" then
|
||||
states[sessionid] = nil;
|
||||
stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message);
|
||||
origin.send(stanza);
|
||||
return true;
|
||||
else
|
||||
cmdtag = command:cmdtag("executing", sessionid);
|
||||
end
|
||||
|
||||
for name, content in pairs(data) do
|
||||
if name == "info" then
|
||||
cmdtag:tag("note", {type="info"}):text(content):up();
|
||||
elseif name == "warn" then
|
||||
cmdtag:tag("note", {type="warn"}):text(content):up();
|
||||
elseif name == "error" then
|
||||
cmdtag:tag("note", {type="error"}):text(content.message):up();
|
||||
elseif name =="actions" then
|
||||
local actions = st.stanza("actions");
|
||||
for _, action in ipairs(content) do
|
||||
if (action == "prev") or (action == "next") or (action == "complete") then
|
||||
actions:tag(action):up();
|
||||
else
|
||||
module:log("error", 'Command "'..command.name..
|
||||
'" at node "'..command.node..'" provided an invalid action "'..action..'"');
|
||||
end
|
||||
end
|
||||
cmdtag:add_child(actions);
|
||||
elseif name == "form" then
|
||||
cmdtag:add_child((content.layout or content):form(content.values));
|
||||
elseif name == "result" then
|
||||
cmdtag:add_child((content.layout or content):form(content.values, "result"));
|
||||
elseif name == "other" then
|
||||
cmdtag:add_child(content);
|
||||
end
|
||||
end
|
||||
stanza:add_child(cmdtag);
|
||||
origin.send(stanza);
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
return _M;
|
145
verse/libs/bit.lua
Normal file
145
verse/libs/bit.lua
Normal file
@ -0,0 +1,145 @@
|
||||
-- Prosody IM
|
||||
-- Copyright (C) 2008-2010 Matthew Wild
|
||||
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||
--
|
||||
-- This project is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
--
|
||||
|
||||
|
||||
local type = type;
|
||||
local tonumber = tonumber;
|
||||
local setmetatable = setmetatable;
|
||||
local error = error;
|
||||
local tostring = tostring;
|
||||
local print = print;
|
||||
|
||||
local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;};
|
||||
local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;};
|
||||
local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;}
|
||||
|
||||
local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;};
|
||||
local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;};
|
||||
local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;};
|
||||
local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;};
|
||||
local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;};
|
||||
local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;};
|
||||
|
||||
module "bit"
|
||||
|
||||
local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end};
|
||||
local function do_bop(a, b, op)
|
||||
return setmetatable({
|
||||
op[a[1]*16+b[1]];
|
||||
op[a[2]*16+b[2]];
|
||||
op[a[3]*16+b[3]];
|
||||
op[a[4]*16+b[4]];
|
||||
op[a[5]*16+b[5]];
|
||||
op[a[6]*16+b[6]];
|
||||
op[a[7]*16+b[7]];
|
||||
op[a[8]*16+b[8]];
|
||||
}, bit_mt);
|
||||
end
|
||||
local function do_uop(a, op)
|
||||
return setmetatable({
|
||||
op[a[1]];
|
||||
op[a[2]];
|
||||
op[a[3]];
|
||||
op[a[4]];
|
||||
op[a[5]];
|
||||
op[a[6]];
|
||||
op[a[7]];
|
||||
op[a[8]];
|
||||
}, bit_mt);
|
||||
end
|
||||
|
||||
function bxor(a, b) return do_bop(a, b, xor_map); end
|
||||
function bor(a, b) return do_bop(a, b, or_map); end
|
||||
function band(a, b) return do_bop(a, b, and_map); end
|
||||
|
||||
function bnot(a) return do_uop(a, not_map); end
|
||||
local function _rshift1(t)
|
||||
local carry = 0;
|
||||
for i=1,8 do
|
||||
local t_i = rshift1_map[t[i]] + carry;
|
||||
carry = rshift1carry_map[t[i]];
|
||||
t[i] = t_i;
|
||||
end
|
||||
end
|
||||
function rshift(a, i)
|
||||
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
|
||||
for n = 1,i do _rshift1(t); end
|
||||
return setmetatable(t, bit_mt);
|
||||
end
|
||||
local function _arshift1(t)
|
||||
local carry = arshift1carry_map[t[1]];
|
||||
for i=1,8 do
|
||||
local t_i = rshift1_map[t[i]] + carry;
|
||||
carry = rshift1carry_map[t[i]];
|
||||
t[i] = t_i;
|
||||
end
|
||||
end
|
||||
function arshift(a, i)
|
||||
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
|
||||
for n = 1,i do _arshift1(t); end
|
||||
return setmetatable(t, bit_mt);
|
||||
end
|
||||
local function _lshift1(t)
|
||||
local carry = 0;
|
||||
for i=8,1,-1 do
|
||||
local t_i = lshift1_map[t[i]] + carry;
|
||||
carry = lshift1carry_map[t[i]];
|
||||
t[i] = t_i;
|
||||
end
|
||||
end
|
||||
function lshift(a, i)
|
||||
local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
|
||||
for n = 1,i do _lshift1(t); end
|
||||
return setmetatable(t, bit_mt);
|
||||
end
|
||||
|
||||
local function _cast(a)
|
||||
if type(a) == "number" then a = ("%x"):format(a);
|
||||
elseif type(a) == "table" then return a;
|
||||
elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end
|
||||
local t = {0,0,0,0,0,0,0,0};
|
||||
a = "00000000"..a;
|
||||
a = a:sub(-8);
|
||||
for i = 1,8 do
|
||||
t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2);
|
||||
end
|
||||
return setmetatable(t, bit_mt);
|
||||
end
|
||||
|
||||
local function wrap1(f)
|
||||
return function(a, ...)
|
||||
if type(a) ~= "table" then a = _cast(a); end
|
||||
a = f(a, ...);
|
||||
a = tonumber(tostring(a), 16);
|
||||
if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
|
||||
return a;
|
||||
end;
|
||||
end
|
||||
local function wrap2(f)
|
||||
return function(a, b, ...)
|
||||
if type(a) ~= "table" then a = _cast(a); end
|
||||
if type(b) ~= "table" then b = _cast(b); end
|
||||
a = f(a, b, ...);
|
||||
a = tonumber(tostring(a), 16);
|
||||
if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
|
||||
return a;
|
||||
end;
|
||||
end
|
||||
|
||||
bxor = wrap2(bxor);
|
||||
bor = wrap2(bor);
|
||||
band = wrap2(band);
|
||||
bnot = wrap1(bnot);
|
||||
lshift = wrap1(lshift);
|
||||
rshift = wrap1(rshift);
|
||||
arshift = wrap1(arshift);
|
||||
cast = wrap1(_cast);
|
||||
|
||||
bits = 32;
|
||||
|
||||
return _M;
|
12
verse/libs/encodings.lua
Normal file
12
verse/libs/encodings.lua
Normal file
@ -0,0 +1,12 @@
|
||||
local function not_impl()
|
||||
error("Function not implemented");
|
||||
end
|
||||
|
||||
local mime = require "mime";
|
||||
|
||||
module "encodings"
|
||||
|
||||
stringprep = {};
|
||||
base64 = { encode = mime.b64, decode = not_impl }; --mime.unb64 is buggy with \0
|
||||
|
||||
return _M;
|
3
verse/libs/hashes.lua
Normal file
3
verse/libs/hashes.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local sha1 = require "util.sha1";
|
||||
|
||||
return { sha1 = sha1.sha1 };
|
27
verse/libs/xstanza.lua
Normal file
27
verse/libs/xstanza.lua
Normal file
@ -0,0 +1,27 @@
|
||||
local stanza_mt = getmetatable(require "util.stanza".stanza());
|
||||
|
||||
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
|
||||
function stanza_mt:get_error()
|
||||
local type, condition, text;
|
||||
|
||||
local error_tag = self:get_child("error");
|
||||
if not error_tag then
|
||||
return nil, nil;
|
||||
end
|
||||
type = error_tag.attr.type;
|
||||
|
||||
for child in error_tag:children() do
|
||||
if child.attr.xmlns == xmlns_stanzas then
|
||||
if child.name == "text" then
|
||||
text = child:get_text();
|
||||
else
|
||||
condition = child.name;
|
||||
end
|
||||
if condition and text then
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
return type, condition, text;
|
||||
end
|
115
verse/plugins/adhoc.lua
Normal file
115
verse/plugins/adhoc.lua
Normal file
@ -0,0 +1,115 @@
|
||||
local verse = require "verse";
|
||||
local adhoc = require "lib.adhoc";
|
||||
|
||||
local xmlns_commands = "http://jabber.org/protocol/commands";
|
||||
local xmlns_data = "jabber:x:data";
|
||||
|
||||
local command_mt = {};
|
||||
command_mt.__index = command_mt;
|
||||
|
||||
-- Table of commands we provide
|
||||
local commands = {};
|
||||
|
||||
function verse.plugins.adhoc(stream)
|
||||
stream:add_plugin("disco");
|
||||
stream:add_disco_feature(xmlns_commands);
|
||||
|
||||
function stream:query_commands(jid, callback)
|
||||
stream:disco_items(jid, xmlns_commands, function (items)
|
||||
stream:debug("adhoc list returned")
|
||||
local command_list = {};
|
||||
for _, item in ipairs(items) do
|
||||
command_list[item.node] = item.name;
|
||||
end
|
||||
stream:debug("adhoc calling callback")
|
||||
return callback(command_list);
|
||||
end);
|
||||
end
|
||||
|
||||
function stream:execute_command(jid, command, callback)
|
||||
local cmd = setmetatable({
|
||||
stream = stream, jid = jid,
|
||||
command = command, callback = callback
|
||||
}, command_mt);
|
||||
return cmd:execute();
|
||||
end
|
||||
|
||||
-- ACL checker for commands we provide
|
||||
local function has_affiliation(jid, aff)
|
||||
if not(aff) or aff == "user" then return true; end
|
||||
if type(aff) == "function" then
|
||||
return aff(jid);
|
||||
end
|
||||
-- TODO: Support 'roster', etc.
|
||||
end
|
||||
|
||||
function stream:add_adhoc_command(name, node, handler, permission)
|
||||
commands[node] = adhoc.new(name, node, handler, permission);
|
||||
stream:add_disco_item({ jid = stream.jid, node = node, name = name }, xmlns_commands);
|
||||
return commands[node];
|
||||
end
|
||||
|
||||
local function handle_command(stanza)
|
||||
local command_tag = stanza.tags[1];
|
||||
local node = command_tag.attr.node;
|
||||
|
||||
local handler = commands[node];
|
||||
if not handler then return; end
|
||||
|
||||
if not has_affiliation(stanza.attr.from, handler.permission) then
|
||||
stream:send(verse.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
|
||||
:add_child(handler:cmdtag("canceled")
|
||||
:tag("note", {type="error"}):text("You don't have permission to execute this command")));
|
||||
return true
|
||||
end
|
||||
|
||||
-- User has permission now execute the command
|
||||
return adhoc.handle_cmd(handler, { send = function (d) return stream:send(d) end }, stanza);
|
||||
end
|
||||
|
||||
stream:hook("iq/"..xmlns_commands, function (stanza)
|
||||
local type = stanza.attr.type;
|
||||
local name = stanza.tags[1].name;
|
||||
if type == "set" and name == "command" then
|
||||
return handle_command(stanza);
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
function command_mt:_process_response(result)
|
||||
if result.attr.type == "error" then
|
||||
self.status = "canceled";
|
||||
self.callback(self, {});
|
||||
return;
|
||||
end
|
||||
local command = result:get_child("command", xmlns_commands);
|
||||
self.status = command.attr.status;
|
||||
self.sessionid = command.attr.sessionid;
|
||||
self.form = command:get_child("x", xmlns_data);
|
||||
self.note = command:get_child("note"); --FIXME handle multiple <note/>s
|
||||
self.callback(self);
|
||||
end
|
||||
|
||||
-- Initial execution of a command
|
||||
function command_mt:execute()
|
||||
local iq = verse.iq({ to = self.jid, type = "set" })
|
||||
:tag("command", { xmlns = xmlns_commands, node = self.command });
|
||||
self.stream:send_iq(iq, function (result)
|
||||
self:_process_response(result);
|
||||
end);
|
||||
end
|
||||
|
||||
function command_mt:next(form)
|
||||
local iq = verse.iq({ to = self.jid, type = "set" })
|
||||
:tag("command", {
|
||||
xmlns = xmlns_commands,
|
||||
node = self.command,
|
||||
sessionid = self.sessionid
|
||||
});
|
||||
|
||||
if form then iq:add_child(form); end
|
||||
|
||||
self.stream:send_iq(iq, function (result)
|
||||
self:_process_response(result);
|
||||
end);
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user