");
if not sid then
-- New session request
context.notopen = nil; -- Signals that we accept this opening tag
local to_host = nameprep(attr.to);
local rid = tonumber(attr.rid);
local wait = tonumber(attr.wait);
if not to_host then
log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" });
response:send(tostring(close_reply));
return;
elseif not hosts[to_host] then
-- Unknown host
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
response:send(tostring(close_reply));
return;
end
if not rid or (not wait and attr.wait or wait < 0 or wait % 1 ~= 0) then
log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
response:send(tostring(close_reply));
return;
end
rid = rid - 1;
wait = math_min(wait, bosh_max_wait);
-- New session
sid = new_uuid();
local session = {
type = "c2s_unauthed", conn = request.conn, sid = sid, rid = rid, host = attr.to,
bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
bosh_max_inactive = bosh_max_inactivity,
requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true,
log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure,
ip = get_ip_from_request(request);
};
sessions[sid] = session;
local filter = initialize_filters(session);
session.log("debug", "BOSH session created for request from %s", session.ip);
log("info", "New BOSH session, assigned it sid '%s'", sid);
hosts[session.host].events.fire_event("bosh-session", { session = session, request = request });
-- Send creation response
local creating_session = true;
local r = session.requests;
function session.send(s)
-- We need to ensure that outgoing stanzas have the jabber:client xmlns
if s.attr and not s.attr.xmlns then
s = st.clone(s);
s.attr.xmlns = "jabber:client";
end
s = filter("stanzas/out", s);
--log("debug", "Sending BOSH data: %s", tostring(s));
if not s then return true end
t_insert(session.send_buffer, tostring(s));
local oldest_request = r[1];
if oldest_request and not session.bosh_processing then
log("debug", "We have an open request, so sending on that");
local body_attr = { xmlns = "http://jabber.org/protocol/httpbind",
["xmlns:stream"] = "http://etherx.jabber.org/streams";
type = session.bosh_terminate and "terminate" or nil;
sid = sid;
};
if creating_session then
creating_session = nil;
body_attr.requests = tostring(BOSH_MAX_REQUESTS);
body_attr.hold = tostring(BOSH_HOLD);
body_attr.inactivity = tostring(bosh_max_inactivity);
body_attr.polling = tostring(bosh_max_polling);
body_attr.wait = tostring(session.bosh_wait);
body_attr.authid = sid;
body_attr.secure = "true";
body_attr.ver = '1.6';
body_attr.from = session.host;
body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh";
body_attr["xmpp:version"] = "1.0";
end
session.bosh_last_response = st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer).."";
oldest_request:send(session.bosh_last_response);
session.send_buffer = {};
end
return true;
end
request.sid = sid;
end
local session = sessions[sid];
if not session then
-- Unknown sid
log("info", "Client tried to use sid '%s' which we don't know about", sid);
response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
context.notopen = nil;
return;
end
session.conn = request.conn;
if session.rid then
local rid = tonumber(attr.rid);
local diff = rid - session.rid;
-- Diff should be 1 for a healthy request
if diff ~= 1 then
context.sid = sid;
context.notopen = nil;
if diff == 2 then
-- Hold request, but don't process it (ouch!)
session.log("debug", "rid skipped: %d, deferring this request", rid-1)
context.defer = true;
session.bosh_deferred = { context = context, sid = sid, rid = rid, terminate = attr.type == "terminate" };
return;
end
context.ignore = true;
if diff == 0 then
-- Re-send previous response, ignore stanzas in this request
session.log("debug", "rid repeated, ignoring: %s (diff %d)", session.rid, diff);
response:send(session.bosh_last_response);
return;
end
-- Session broken, destroy it
session.log("debug", "rid out of range: %d (diff %d)", rid, diff);
response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
return;
end
session.rid = rid;
end
if attr.type == "terminate" then
-- Client wants to end this session, which we'll do
-- after processing any stanzas in this request
session.bosh_terminate = true;
end
context.notopen = nil; -- Signals that we accept this opening tag
t_insert(session.requests, response);
context.sid = sid;
session.bosh_processing = true; -- Used to suppress replies until processing of this request is done
if session.notopen then
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
session.send(features);
session.notopen = nil;
end
end
local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end
function stream_callbacks.handlestanza(context, stanza)
if context.ignore then return; end
log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
local session = sessions[context.sid];
if session then
if stanza.attr.xmlns == xmlns_bosh then
stanza.attr.xmlns = nil;
end
if context.defer and session.bosh_deferred then
log("debug", "Deferring this stanza");
t_insert(session.bosh_deferred, stanza);
else
stanza = session.filter("stanzas/in", stanza);
if stanza then
return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
end
end
else
log("debug", "No session for this stanza! (sid: %s)", context.sid or "none!");
end
end
function stream_callbacks.streamclosed(context)
local session = sessions[context.sid];
if session then
if not context.defer and session.bosh_deferred then
-- Handle deferred stanzas now
local deferred_stanzas = session.bosh_deferred;
local context = deferred_stanzas.context;
session.bosh_deferred = nil;
log("debug", "Handling deferred stanzas from rid %d", deferred_stanzas.rid);
session.rid = deferred_stanzas.rid;
t_insert(session.requests, context.response);
for _, stanza in ipairs(deferred_stanzas) do
stream_callbacks.handlestanza(context, stanza);
end
if deferred_stanzas.terminate then
session.bosh_terminate = true;
end
end
session.bosh_processing = false;
if #session.send_buffer > 0 then
session.send("");
end
end
end
function stream_callbacks.error(context, error)
log("debug", "Error parsing BOSH request payload; %s", error);
if not context.sid then
local response = context.response;
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
response:send(tostring(close_reply));
return;
end
local session = sessions[context.sid];
if error == "stream-error" then -- Remote stream error, we close normally
session:close();
else
session:close({ condition = "bad-format", text = "Error processing stream" });
end
end
local dead_sessions = module:shared("dead_sessions");
function on_timer()
-- log("debug", "Checking for requests soon to timeout...");
-- Identify requests timing out within the next few seconds
local now = os_time() + 3;
for request, reply_before in pairs(waiting_requests) do
if reply_before <= now then
log("debug", "%s was soon to timeout (at %d, now %d), sending empty response", tostring(request), reply_before, now);
-- Send empty response to let the
-- client know we're still here
if request.conn then
sessions[request.context.sid].send("");
end
end
end
now = now - 3;
local n_dead_sessions = 0;
for session, close_after in pairs(inactive_sessions) do
if close_after < now then
(session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
sessions[session.sid] = nil;
inactive_sessions[session] = nil;
n_dead_sessions = n_dead_sessions + 1;
dead_sessions[n_dead_sessions] = session;
end
end
for i=1,n_dead_sessions do
local session = dead_sessions[i];
dead_sessions[i] = nil;
sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds");
end
return 1;
end
module:add_timer(1, on_timer);
local GET_response = {
headers = {
content_type = "text/html";
};
body = [[
It works! Now point your BOSH client to this URL to connect to Prosody.
For more information see Prosody: Setting up BOSH.
]];
};
function module.add_host(module)
module:depends("http");
module:provides("http", {
default_path = "/http-bind";
route = {
["GET"] = GET_response;
["GET /"] = GET_response;
["OPTIONS"] = handle_OPTIONS;
["OPTIONS /"] = handle_OPTIONS;
["POST"] = handle_POST;
["POST /"] = handle_POST;
};
});
end
prosody-0.10.0/plugins/mod_mam/ 0000775 0001750 0001750 00000000000 13163172043 016267 5 ustar matthew matthew prosody-0.10.0/plugins/mod_mam/mamprefs.lib.lua 0000644 0001750 0001750 00000003116 13163172043 021350 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2011-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- XEP-0313: Message Archive Management for Prosody
--
-- luacheck: ignore 122/prosody
local global_default_policy = module:get_option_string("default_archive_policy", true);
if global_default_policy ~= "roster" then
global_default_policy = module:get_option_boolean("default_archive_policy", global_default_policy);
end
do
-- luacheck: ignore 211/prefs_format
local prefs_format = {
[false] = "roster",
-- default ::= true | false | "roster"
-- true = always, false = never, nil = global default
["romeo@montague.net"] = true, -- always
["montague@montague.net"] = false, -- newer
};
end
local sessions = prosody.hosts[module.host].sessions;
local archive_store = module:get_option_string("archive_store", "archive");
local prefs = module:open_store(archive_store .. "_prefs");
local function get_prefs(user)
local user_sessions = sessions[user];
local user_prefs = user_sessions and user_sessions.archive_prefs
if not user_prefs and user_sessions then
user_prefs = prefs:get(user);
user_sessions.archive_prefs = user_prefs;
end
return user_prefs or { [false] = global_default_policy };
end
local function set_prefs(user, user_prefs)
local user_sessions = sessions[user];
if user_sessions then
user_sessions.archive_prefs = user_prefs;
end
return prefs:set(user, user_prefs);
end
return {
get = get_prefs,
set = set_prefs,
}
prosody-0.10.0/plugins/mod_mam/fallback_archive.lib.lua 0000644 0001750 0001750 00000005062 13163172043 023000 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2011-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- luacheck: ignore 212/self
local uuid = require "util.uuid".generate;
local store = module:shared("archive");
local archive_store = { _provided_by = "mam"; name = "fallback"; };
function archive_store:append(username, key, value, when, with)
local archive = store[username];
if not archive then
archive = { [0] = 0 };
store[username] = archive;
end
local index = (archive[0] or #archive)+1;
local item = { key = key, when = when, with = with, value = value };
if not key or archive[key] then
key = uuid();
item.key = key;
end
archive[index] = item;
archive[key] = index;
archive[0] = index;
return key;
end
function archive_store:find(username, query)
local archive = store[username] or {};
local start, stop, step = 1, archive[0] or #archive, 1;
local qstart, qend, qwith = -math.huge, math.huge;
local limit;
if query then
if query.reverse then
start, stop, step = stop, start, -1;
if query.before and archive[query.before] then
start = archive[query.before] - 1;
end
elseif query.after and archive[query.after] then
start = archive[query.after] + 1;
end
qwith = query.with;
limit = query.limit;
qstart = query.start or qstart;
qend = query["end"] or qend;
end
return function ()
if limit and limit <= 0 then return end
for i = start, stop, step do
local item = archive[i];
if (not qwith or qwith == item.with) and item.when >= qstart and item.when <= qend then
if limit then limit = limit - 1; end
start = i + step; -- Start on next item
return item.key, item.value, item.when, item.with;
end
end
end
end
function archive_store:delete(username, query)
if not query or next(query) == nil then
-- no specifics, delete everything
store[username] = nil;
return true;
end
local archive = store[username];
if not archive then return true; end -- no messages, nothing to delete
local qstart = query.start or -math.huge;
local qend = query["end"] or math.huge;
local qwith = query.with;
store[username] = nil;
for i = 1, #archive do
local item = archive[i];
local when, with = item.when, item.when;
-- Add things that don't match the query
if not ((not qwith or qwith == item.with) and item.when >= qstart and item.when <= qend) then
self:append(username, item.key, item.value, when, with);
end
end
return true;
end
return archive_store;
prosody-0.10.0/plugins/mod_mam/mod_mam.lua 0000644 0001750 0001750 00000032061 13163172043 020403 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2011-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- XEP-0313: Message Archive Management for Prosody
--
local xmlns_mam = "urn:xmpp:mam:2";
local xmlns_delay = "urn:xmpp:delay";
local xmlns_forward = "urn:xmpp:forward:0";
local xmlns_st_id = "urn:xmpp:sid:0";
local um = require "core.usermanager";
local st = require "util.stanza";
local rsm = require "util.rsm";
local get_prefs = module:require"mamprefs".get;
local set_prefs = module:require"mamprefs".set;
local prefs_to_stanza = module:require"mamprefsxml".tostanza;
local prefs_from_stanza = module:require"mamprefsxml".fromstanza;
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local jid_prepped_split = require "util.jid".prepped_split;
local dataform = require "util.dataforms".new;
local host = module.host;
local rm_load_roster = require "core.rostermanager".load_roster;
local is_stanza = st.is_stanza;
local tostring = tostring;
local time_now = os.time;
local m_min = math.min;
local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" });
local archive_store = module:get_option_string("archive_store", "archive");
local archive = module:open_store(archive_store, "archive");
if archive.name == "null" or not archive.find then
if not archive.find then
module:log("debug", "Attempt to open archive storage returned a valid driver but it does not seem to implement the storage API");
module:log("debug", "mod_%s does not support archiving", archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or "");
else
module:log("debug", "Attempt to open archive storage returned null driver");
end
module:log("debug", "See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information");
module:log("info", "Using in-memory fallback archive driver");
archive = module:require "fallback_archive";
end
local use_total = true;
local cleanup;
local function schedule_cleanup(username)
if cleanup and not cleanup[username] then
table.insert(cleanup, username);
cleanup[username] = true;
end
end
-- Handle prefs.
module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
local origin, stanza = event.origin, event.stanza;
local user = origin.username;
if stanza.attr.type == "set" then
local new_prefs = stanza:get_child("prefs", xmlns_mam);
local prefs = prefs_from_stanza(new_prefs);
local ok, err = set_prefs(user, prefs);
if not ok then
origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err)));
return true;
end
end
local prefs = prefs_to_stanza(get_prefs(user));
local reply = st.reply(stanza):add_child(prefs);
origin.send(reply);
return true;
end);
local query_form = dataform {
{ name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam; };
{ name = "with"; type = "jid-single"; };
{ name = "start"; type = "text-single" };
{ name = "end"; type = "text-single"; };
};
-- Serve form
module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
local origin, stanza = event.origin, event.stanza;
origin.send(st.reply(stanza):query(xmlns_mam):add_child(query_form:form()));
return true;
end);
-- Handle archive queries
module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local origin, stanza = event.origin, event.stanza;
local query = stanza.tags[1];
local qid = query.attr.queryid;
schedule_cleanup(origin.username);
-- Search query parameters
local qwith, qstart, qend;
local form = query:get_child("x", "jabber:x:data");
if form then
local err;
form, err = query_form:data(form);
if err then
origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
return true;
end
qwith, qstart, qend = form["with"], form["start"], form["end"];
qwith = qwith and jid_bare(qwith); -- dataforms does jidprep
end
if qstart or qend then -- Validate timestamps
local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend));
if (qstart and not vstart) or (qend and not vend) then
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"))
return true;
end
qstart, qend = vstart, vend;
end
module:log("debug", "Archive query, id %s with %s from %s until %s)",
tostring(qid), qwith or "anyone",
qstart and timestamp(qstart) or "the dawn of time",
qend and timestamp(qend) or "now");
-- RSM stuff
local qset = rsm.get(query);
local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
local reverse = qset and qset.before or false;
local before, after = qset and qset.before, qset and qset.after;
if type(before) ~= "string" then before = nil; end
-- Load all the data!
local data, err = archive:find(origin.username, {
start = qstart; ["end"] = qend; -- Time range
with = qwith;
limit = qmax + 1;
before = before; after = after;
reverse = reverse;
total = use_total or qmax == 0;
});
if not data then
origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
return true;
end
local total = tonumber(err);
local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
local results = {};
-- Wrap it in stuff and deliver
local first, last;
local count = 0;
local complete = "true";
for id, item, when in data do
count = count + 1;
if count > qmax then
complete = nil;
break;
end
local fwd_st = st.message(msg_reply_attr)
:tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
:tag("forwarded", { xmlns = xmlns_forward })
:tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
if not is_stanza(item) then
item = st.deserialize(item);
end
item.attr.xmlns = "jabber:client";
fwd_st:add_child(item);
if not first then first = id; end
last = id;
if reverse then
results[count] = fwd_st;
else
origin.send(fwd_st);
end
end
if reverse then
for i = #results, 1, -1 do
origin.send(results[i]);
end
first, last = last, first;
end
-- That's all folks!
module:log("debug", "Archive query %s completed", tostring(qid));
origin.send(st.reply(stanza)
:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
:add_child(rsm.generate {
first = first, last = last, count = total }));
return true;
end);
local function has_in_roster(user, who)
local roster = rm_load_roster(user, host);
module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no");
return roster[who];
end
local function shall_store(user, who)
-- TODO Cache this?
if not um.user_exists(user, host) then
return false;
end
local prefs = get_prefs(user);
local rule = prefs[who];
module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule));
if rule ~= nil then
return rule;
end
-- Below could be done by a metatable
local default = prefs[false];
module:log("debug", "%s's default rule is %s", user, tostring(default));
if default == "roster" then
return has_in_roster(user, who);
end
return default;
end
local function strip_stanza_id(stanza, user)
if stanza:get_child("stanza-id", xmlns_st_id) then
stanza = st.clone(stanza);
stanza:maptags(function (tag)
if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id then
local by_user, by_host, res = jid_prepped_split(tag.attr.by);
if not res and by_host == host and by_user == user then
return nil;
end
end
return tag;
end);
end
return stanza;
end
-- Handle messages
local function message_handler(event, c2s)
local origin, stanza = event.origin, event.stanza;
local log = c2s and origin.log or module._log;
local orig_type = stanza.attr.type or "normal";
local orig_from = stanza.attr.from;
local orig_to = stanza.attr.to or orig_from;
-- Stanza without 'to' are treated as if it was to their own bare jid
-- Whos storage do we put it in?
local store_user = c2s and origin.username or jid_split(orig_to);
-- And who are they chatting with?
local with = jid_bare(c2s and orig_to or orig_from);
-- Filter out that claim to be from us
event.stanza = strip_stanza_id(stanza, store_user);
-- We store chat messages or normal messages that have a body
if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then
log("debug", "Not archiving stanza: %s (type)", stanza:top_tag());
return;
end
-- or if hints suggest we shouldn't
if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store
if stanza:get_child("no-permanent-store", "urn:xmpp:hints")
or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store
log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag());
return;
end
end
local clone_for_storage;
if not strip_tags:empty() then
clone_for_storage = st.clone(stanza);
clone_for_storage:maptags(function (tag)
if strip_tags:contains(tag.attr.xmlns) then
return nil;
else
return tag;
end
end);
if #clone_for_storage.tags == 0 then
log("debug", "Not archiving stanza: %s (empty when stripped)", stanza:top_tag());
return;
end
else
clone_for_storage = stanza;
end
-- Check with the users preferences
if shall_store(store_user, with) then
log("debug", "Archiving stanza: %s", stanza:top_tag());
-- And stash it
local ok = archive:append(store_user, nil, clone_for_storage, time_now(), with);
if ok then
local clone_for_other_handlers = st.clone(stanza);
local id = ok;
clone_for_other_handlers:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up();
event.stanza = clone_for_other_handlers;
schedule_cleanup(store_user);
module:fire_event("archive-message-added", { origin = origin, stanza = clone_for_storage, for_user = store_user, id = id });
end
else
log("debug", "Not archiving stanza: %s (prefs)", stanza:top_tag());
end
end
local function c2s_message_handler(event)
return message_handler(event, true);
end
-- Filter out before the message leaves the server to prevent privacy leak.
local function strip_stanza_id_after_other_events(event)
event.stanza = strip_stanza_id(event.stanza, event.origin.username);
end
module:hook("pre-message/bare", strip_stanza_id_after_other_events, -1);
module:hook("pre-message/full", strip_stanza_id_after_other_events, -1);
local cleanup_after = module:get_option_string("archive_expires_after", "1w");
local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
if not archive.delete then
module:log("debug", "Selected storage driver does not support deletion, archives will not expire");
elseif cleanup_after ~= "never" then
local day = 86400;
local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day };
local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)");
if not n then
module:log("error", "Could not parse archive_expires_after string %q", cleanup_after);
return false;
end
cleanup_after = tonumber(n) * ( multipliers[m] or 1 );
module:log("debug", "archive_expires_after = %d -- in seconds", cleanup_after);
if not archive.delete then
module:log("error", "archive_expires_after set but mod_%s does not support deleting", archive._provided_by);
return false;
end
-- Set of known users to do message expiry for
-- Populated either below or when new messages are added
cleanup = {};
-- Iterating over users is not supported by all authentication modules
-- Catch and ignore error if not supported
pcall(function ()
-- If this works, then we schedule cleanup for all known users on startup
for user in um.users(module.host) do
schedule_cleanup(user);
end
end);
-- At odd intervals, delete old messages for one user
module:add_timer(math.random(10, 60), function()
local user = table.remove(cleanup, 1);
if user then
module:log("debug", "Removing old messages for user %q", user);
local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; })
if not ok then
module:log("warn", "Could not expire archives for user %s: %s", user, err);
elseif type(ok) == "number" then
module:log("debug", "Removed %d messages", ok);
end
cleanup[user] = nil;
end
return math.random(cleanup_interval, cleanup_interval * 2);
end);
else
-- Don't ask the backend to count the potentially unbounded number of items,
-- it'll get slow.
use_total = false;
end
-- Stanzas sent by local clients
module:hook("pre-message/bare", c2s_message_handler, 0);
module:hook("pre-message/full", c2s_message_handler, 0);
-- Stanzas to local clients
module:hook("message/bare", message_handler, 0);
module:hook("message/full", message_handler, 0);
module:hook("account-disco-info", function(event)
(event.reply or event.stanza):tag("feature", {var=xmlns_mam}):up();
(event.reply or event.stanza):tag("feature", {var=xmlns_st_id}):up();
end);
prosody-0.10.0/plugins/mod_mam/mamprefsxml.lib.lua 0000644 0001750 0001750 00000002757 13163172043 022103 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2011-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- XEP-0313: Message Archive Management for Prosody
--
local st = require"util.stanza";
local xmlns_mam = "urn:xmpp:mam:2";
local default_attrs = {
always = true, [true] = "always",
never = false, [false] = "never",
roster = "roster",
}
local function tostanza(prefs)
local default = prefs[false];
default = default_attrs[default];
local prefstanza = st.stanza("prefs", { xmlns = xmlns_mam, default = default });
local always = st.stanza("always");
local never = st.stanza("never");
for jid, choice in pairs(prefs) do
if jid then
(choice and always or never):tag("jid"):text(jid):up();
end
end
prefstanza:add_child(always):add_child(never);
return prefstanza;
end
local function fromstanza(prefstanza)
local prefs = {};
local default = prefstanza.attr.default;
if default then
prefs[false] = default_attrs[default];
end
local always = prefstanza:get_child("always");
if always then
for rule in always:childtags("jid") do
local jid = rule:get_text();
prefs[jid] = true;
end
end
local never = prefstanza:get_child("never");
if never then
for rule in never:childtags("jid") do
local jid = rule:get_text();
prefs[jid] = false;
end
end
return prefs;
end
return {
tostanza = tostanza;
fromstanza = fromstanza;
}
prosody-0.10.0/plugins/mod_dialback.lua 0000644 0001750 0001750 00000015572 13163172043 017762 0 ustar matthew matthew -- 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 hosts = _G.hosts;
local log = module._log;
local st = require "util.stanza";
local sha256_hash = require "util.hashes".sha256;
local sha256_hmac = require "util.hashes".hmac_sha256;
local nameprep = require "util.encodings".stringprep.nameprep;
local check_cert_status = module:depends"s2s".check_cert_status;
local uuid_gen = require"util.uuid".generate;
local xmlns_stream = "http://etherx.jabber.org/streams";
local dialback_requests = setmetatable({}, { __mode = 'v' });
local dialback_secret = sha256_hash(module:get_option_string("dialback_secret", uuid_gen()), true);
local dwd = module:get_option_boolean("dialback_without_dialback", false);
function module.save()
return { dialback_secret = dialback_secret };
end
function module.restore(state)
dialback_secret = state.dialback_secret;
end
function generate_dialback(id, to, from)
return sha256_hmac(dialback_secret, to .. ' ' .. from .. ' ' .. id, true);
end
function initiate_dialback(session)
-- generate dialback key
session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
session.sends2s(st.stanza("db:result", { from = session.from_host, to = session.to_host }):text(session.dialback_key));
session.log("debug", "sent dialback key on outgoing s2s stream");
end
function verify_dialback(id, to, from, key)
return key == generate_dialback(id, to, from);
end
module:hook("stanza/jabber:server:dialback:verify", function(event)
local origin, stanza = event.origin, event.stanza;
if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
-- We are being asked to verify the key, to ensure it was generated by us
origin.log("debug", "verifying that dialback key is ours...");
local attr = stanza.attr;
if attr.type then
module:log("warn", "Ignoring incoming session from %s claiming a dialback key for %s is %s",
origin.from_host or "(unknown)", attr.from or "(unknown)", attr.type);
return true;
end
-- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34
--if attr.from ~= origin.to_host then error("invalid-from"); end
local type;
if verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
type = "valid"
else
type = "invalid"
origin.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to);
end
origin.log("debug", "verified dialback key... it is %s", type);
origin.sends2s(st.stanza("db:verify", { from = attr.to, to = attr.from, id = attr.id, type = type }):text(stanza[1]));
return true;
end
end);
module:hook("stanza/jabber:server:dialback:result", function(event)
local origin, stanza = event.origin, event.stanza;
if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then
-- he wants to be identified through dialback
-- We need to check the key with the Authoritative server
local attr = stanza.attr;
local to, from = nameprep(attr.to), nameprep(attr.from);
if not hosts[to] then
-- Not a host that we serve
origin.log("warn", "%s tried to connect to %s, which we don't serve", from, to);
origin:close("host-unknown");
return true;
elseif not from then
origin:close("improper-addressing");
end
if dwd and origin.secure then
if check_cert_status(origin, from) == false then
return
elseif origin.cert_chain_status == "valid" and origin.cert_identity_status == "valid" then
origin.sends2s(st.stanza("db:result", { to = from, from = to, id = attr.id, type = "valid" }));
module:fire_event("s2s-authenticated", { session = origin, host = from });
return true;
end
end
origin.hosts[from] = { dialback_key = stanza[1] };
dialback_requests[from.."/"..origin.streamid] = origin;
-- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from'
-- on streams. We fill in the session's to/from here instead.
if not origin.from_host then
origin.from_host = from;
end
if not origin.to_host then
origin.to_host = to;
end
origin.log("debug", "asking %s if key %s belongs to them", from, stanza[1]);
module:fire_event("route/remote", {
from_host = to, to_host = from;
stanza = st.stanza("db:verify", { from = to, to = from, id = origin.streamid }):text(stanza[1]);
});
return true;
end
end);
module:hook("stanza/jabber:server:dialback:verify", function(event)
local origin, stanza = event.origin, event.stanza;
if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
local attr = stanza.attr;
local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")];
if dialback_verifying and attr.from == origin.to_host then
local valid;
if attr.type == "valid" then
module:fire_event("s2s-authenticated", { session = dialback_verifying, host = attr.from });
valid = "valid";
else
-- Warn the original connection that is was not verified successfully
log("warn", "authoritative server for %s denied the key", attr.from or "(unknown)");
valid = "invalid";
end
if dialback_verifying.destroyed then
log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the db result", tostring(dialback_verifying):match("%w+$"));
else
dialback_verifying.sends2s(
st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid })
:text(dialback_verifying.hosts[attr.from].dialback_key));
end
dialback_requests[attr.from.."/"..(attr.id or "")] = nil;
end
return true;
end
end);
module:hook("stanza/jabber:server:dialback:result", function(event)
local origin, stanza = event.origin, event.stanza;
if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
-- Remote server is telling us whether we passed dialback
local attr = stanza.attr;
if not hosts[attr.to] then
origin:close("host-unknown");
return true;
elseif hosts[attr.to].s2sout[attr.from] ~= origin then
-- This isn't right
origin:close("invalid-id");
return true;
end
if stanza.attr.type == "valid" then
module:fire_event("s2s-authenticated", { session = origin, host = attr.from });
else
origin:close("not-authorized", "dialback authentication failed");
end
return true;
end
end);
module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
if not origin.external_auth or origin.external_auth == "failed" then
module:log("debug", "Initiating dialback...");
initiate_dialback(origin);
return true;
end
end, 100);
module:hook("s2sout-authenticate-legacy", function (event)
module:log("debug", "Initiating dialback...");
initiate_dialback(event.origin);
return true;
end, 100);
-- Offer dialback to incoming hosts
module:hook("s2s-stream-features", function (data)
data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up();
end);
prosody-0.10.0/.hg_archival.txt 0000644 0001750 0001750 00000000263 13163172043 016262 0 ustar matthew matthew repo: 3e3171b59028ee70122cfec6ecf98f518f946b59
node: 39966cbc29f46d7ae9660edca8683d5121c82edf
branch: default
latesttag: 0.9.12
latesttagdistance: 365
changessincelatesttag: 1716
prosody-0.10.0/AUTHORS 0000644 0001750 0001750 00000000567 13163172043 014253 0 ustar matthew matthew
The Prosody project is open to contributions (see HACKERS file), but is
maintained daily by:
- Matthew Wild (mail: matthew [at] prosody.im)
- Waqas Hussain (mail: waqas [at] prosody.im)
- Kim Alvefur (mail: zash [at] prosody.im)
You can reach us collectively by email: developers [at] prosody.im
or in realtime in the Prosody chatroom: prosody@conference.prosody.im
prosody-0.10.0/util/ 0000775 0001750 0001750 00000000000 13163172043 014152 5 ustar matthew matthew prosody-0.10.0/util/prosodyctl.lua 0000644 0001750 0001750 00000014411 13163172043 017056 0 ustar matthew matthew -- 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 config = require "core.configmanager";
local encodings = require "util.encodings";
local stringprep = encodings.stringprep;
local storagemanager = require "core.storagemanager";
local usermanager = require "core.usermanager";
local signal = require "util.signal";
local set = require "util.set";
local lfs = require "lfs";
local pcall = pcall;
local type = type;
local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
local io, os = io, os;
local print = print;
local tonumber = tonumber;
local CFG_SOURCEDIR = _G.CFG_SOURCEDIR;
local _G = _G;
local prosody = prosody;
-- UI helpers
local function show_message(msg, ...)
print(msg:format(...));
end
local function show_usage(usage, desc)
print("Usage: ".._G.arg[0].." "..usage);
if desc then
print(" "..desc);
end
end
local function getchar(n)
local stty_ret = os.execute("stty raw -echo 2>/dev/null");
local ok, char;
if stty_ret == true or stty_ret == 0 then
ok, char = pcall(io.read, n or 1);
os.execute("stty sane");
else
ok, char = pcall(io.read, "*l");
if ok then
char = char:sub(1, n or 1);
end
end
if ok then
return char;
end
end
local function getline()
local ok, line = pcall(io.read, "*l");
if ok then
return line;
end
end
local function getpass()
local stty_ret = os.execute("stty -echo 2>/dev/null");
if stty_ret ~= 0 then
io.write("\027[08m"); -- ANSI 'hidden' text attribute
end
local ok, pass = pcall(io.read, "*l");
if stty_ret == 0 then
os.execute("stty sane");
else
io.write("\027[00m");
end
io.write("\n");
if ok then
return pass;
end
end
local function show_yesno(prompt)
io.write(prompt, " ");
local choice = getchar():lower();
io.write("\n");
if not choice:match("%a") then
choice = prompt:match("%[.-(%U).-%]$");
if not choice then return nil; end
end
return (choice == "y");
end
local function read_password()
local password;
while true do
io.write("Enter new password: ");
password = getpass();
if not password then
show_message("No password - cancelled");
return;
end
io.write("Retype new password: ");
if getpass() ~= password then
if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
return;
end
else
break;
end
end
return password;
end
local function show_prompt(prompt)
io.write(prompt, " ");
local line = getline();
line = line and line:gsub("\n$","");
return (line and #line > 0) and line or nil;
end
-- Server control
local function adduser(params)
local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
if not user then
return false, "invalid-username";
elseif not host then
return false, "invalid-hostname";
end
local host_session = prosody.hosts[host];
if not host_session then
return false, "no-such-host";
end
storagemanager.initialize_host(host);
local provider = host_session.users;
if not(provider) or provider.name == "null" then
usermanager.initialize_host(host);
end
local ok, errmsg = usermanager.create_user(user, password, host);
if not ok then
return false, errmsg or "creating-user-failed";
end
return true;
end
local function user_exists(params)
local user, host = nodeprep(params.user), nameprep(params.host);
storagemanager.initialize_host(host);
local provider = prosody.hosts[host].users;
if not(provider) or provider.name == "null" then
usermanager.initialize_host(host);
end
return usermanager.user_exists(user, host);
end
local function passwd(params)
if not user_exists(params) then
return false, "no-such-user";
end
return adduser(params);
end
local function deluser(params)
if not user_exists(params) then
return false, "no-such-user";
end
local user, host = nodeprep(params.user), nameprep(params.host);
return usermanager.delete_user(user, host);
end
local function getpid()
local pidfile = config.get("*", "pidfile");
if not pidfile then
return false, "no-pidfile";
end
if type(pidfile) ~= "string" then
return false, "invalid-pidfile";
end
pidfile = config.resolve_relative_path(prosody.paths.data, pidfile);
local modules_enabled = set.new(config.get("*", "modules_disabled"));
if prosody.platform ~= "posix" or modules_enabled:contains("posix") then
return false, "no-posix";
end
local file, err = io.open(pidfile, "r+");
if not file then
return false, "pidfile-read-failed", err;
end
local locked, err = lfs.lock(file, "w");
if locked then
file:close();
return false, "pidfile-not-locked";
end
local pid = tonumber(file:read("*a"));
file:close();
if not pid then
return false, "invalid-pid";
end
return true, pid;
end
local function isrunning()
local ok, pid, err = getpid();
if not ok then
if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then
-- Report as not running, since we can't open the pidfile
-- (it probably doesn't exist)
return true, false;
end
return ok, pid;
end
return true, signal.kill(pid, 0) == 0;
end
local function start()
local ok, ret = isrunning();
if not ok then
return ok, ret;
end
if ret then
return false, "already-running";
end
if not CFG_SOURCEDIR then
os.execute("./prosody");
else
os.execute(CFG_SOURCEDIR.."/../../bin/prosody");
end
return true;
end
local function stop()
local ok, ret = isrunning();
if not ok then
return ok, ret;
end
if not ret then
return false, "not-running";
end
local ok, pid = getpid()
if not ok then return false, pid; end
signal.kill(pid, signal.SIGTERM);
return true;
end
local function reload()
local ok, ret = isrunning();
if not ok then
return ok, ret;
end
if not ret then
return false, "not-running";
end
local ok, pid = getpid()
if not ok then return false, pid; end
signal.kill(pid, signal.SIGHUP);
return true;
end
return {
show_message = show_message;
show_warning = show_message;
show_usage = show_usage;
getchar = getchar;
getline = getline;
getpass = getpass;
show_yesno = show_yesno;
read_password = read_password;
show_prompt = show_prompt;
adduser = adduser;
user_exists = user_exists;
passwd = passwd;
deluser = deluser;
getpid = getpid;
isrunning = isrunning;
start = start;
stop = stop;
reload = reload;
};
prosody-0.10.0/util/sql.lua 0000644 0001750 0001750 00000026221 13163172043 015455 0 ustar matthew matthew
local setmetatable, getmetatable = setmetatable, getmetatable;
local ipairs, unpack, select = ipairs, table.unpack or unpack, select; --luacheck: ignore 113
local tonumber, tostring = tonumber, tostring;
local type = type;
local assert, pcall, xpcall, debug_traceback = assert, pcall, xpcall, debug.traceback;
local t_concat = table.concat;
local s_char = string.char;
local log = require "util.logger".init("sql");
local DBI = require "DBI";
-- This loads all available drivers while globals are unlocked
-- LuaDBI should be fixed to not set globals.
DBI.Drivers();
local build_url = require "socket.url".build;
local _ENV = nil;
local column_mt = {};
local table_mt = {};
local query_mt = {};
--local op_mt = {};
local index_mt = {};
local function is_column(x) return getmetatable(x)==column_mt; end
local function is_index(x) return getmetatable(x)==index_mt; end
local function is_table(x) return getmetatable(x)==table_mt; end
local function is_query(x) return getmetatable(x)==query_mt; end
local function Integer() return "Integer()" end
local function String() return "String()" end
local function Column(definition)
return setmetatable(definition, column_mt);
end
local function Table(definition)
local c = {}
for i,col in ipairs(definition) do
if is_column(col) then
c[i], c[col.name] = col, col;
elseif is_index(col) then
col.table = definition.name;
end
end
return setmetatable({ __table__ = definition, c = c, name = definition.name }, table_mt);
end
local function Index(definition)
return setmetatable(definition, index_mt);
end
function table_mt:__tostring()
local s = { 'name="'..self.__table__.name..'"' }
for _, col in ipairs(self.__table__) do
s[#s+1] = tostring(col);
end
return 'Table{ '..t_concat(s, ", ")..' }'
end
table_mt.__index = {};
function table_mt.__index:create(engine)
return engine:_create_table(self);
end
function table_mt:__call(...)
-- TODO
end
function column_mt:__tostring()
return 'Column{ name="'..self.name..'", type="'..self.type..'" }'
end
function index_mt:__tostring()
local s = 'Index{ name="'..self.name..'"';
for i=1,#self do s = s..', "'..self[i]:gsub("[\\\"]", "\\%1")..'"'; end
return s..' }';
-- return 'Index{ name="'..self.name..'", type="'..self.type..'" }'
end
local function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return s_char(tonumber(c,16)); end)); end
local function parse_url(url)
local scheme, secondpart, database = url:match("^([%w%+]+)://([^/]*)/?(.*)");
assert(scheme, "Invalid URL format");
local username, password, host, port;
local authpart, hostpart = secondpart:match("([^@]+)@([^@+])");
if not authpart then hostpart = secondpart; end
if authpart then
username, password = authpart:match("([^:]*):(.*)");
username = username or authpart;
password = password and urldecode(password);
end
if hostpart then
host, port = hostpart:match("([^:]*):(.*)");
host = host or hostpart;
port = port and assert(tonumber(port), "Invalid URL format");
end
return {
scheme = scheme:lower();
username = username; password = password;
host = host; port = port;
database = #database > 0 and database or nil;
};
end
local engine = {};
function engine:connect()
if self.conn then return true; end
local params = self.params;
assert(params.driver, "no driver")
log("debug", "Connecting to [%s] %s...", params.driver, params.database);
local ok, dbh, err = pcall(DBI.Connect,
params.driver, params.database,
params.username, params.password,
params.host, params.port
);
if not ok then return ok, dbh; end
if not dbh then return nil, err; end
dbh:autocommit(false); -- don't commit automatically
self.conn = dbh;
self.prepared = {};
local ok, err = self:set_encoding();
if not ok then
return ok, err;
end
local ok, err = self:onconnect();
if ok == false then
return ok, err;
end
return true;
end
function engine:onconnect()
-- Override from create_engine()
end
function engine:prepquery(sql)
if self.params.driver == "MySQL" then
sql = sql:gsub("\"", "`");
end
return sql;
end
function engine:execute(sql, ...)
local success, err = self:connect();
if not success then return success, err; end
local prepared = self.prepared;
sql = self:prepquery(sql);
local stmt = prepared[sql];
if not stmt then
local err;
stmt, err = self.conn:prepare(sql);
if not stmt then return stmt, err; end
prepared[sql] = stmt;
end
local success, err = stmt:execute(...);
if not success then return success, err; end
return stmt;
end
local result_mt = { __index = {
affected = function(self) return self.__stmt:affected(); end;
rowcount = function(self) return self.__stmt:rowcount(); end;
} };
local function debugquery(where, sql, ...)
local i = 0; local a = {...}
sql = sql:gsub("\n?\t+", " ");
log("debug", "[%s] %s", where, sql:gsub("%?", function ()
i = i + 1;
local v = a[i];
if type(v) == "string" then
v = ("'%s'"):format(v:gsub("'", "''"));
end
return tostring(v);
end));
end
function engine:execute_query(sql, ...)
sql = self:prepquery(sql);
local stmt = assert(self.conn:prepare(sql));
assert(stmt:execute(...));
local result = {};
for row in stmt:rows() do result[#result + 1] = row; end
stmt:close();
local i = 0;
return function() i=i+1; return result[i]; end;
end
function engine:execute_update(sql, ...)
sql = self:prepquery(sql);
local prepared = self.prepared;
local stmt = prepared[sql];
if not stmt then
stmt = assert(self.conn:prepare(sql));
prepared[sql] = stmt;
end
assert(stmt:execute(...));
return setmetatable({ __stmt = stmt }, result_mt);
end
engine.insert = engine.execute_update;
engine.select = engine.execute_query;
engine.delete = engine.execute_update;
engine.update = engine.execute_update;
local function debugwrap(name, f)
return function (self, sql, ...)
debugquery(name, sql, ...)
return f(self, sql, ...)
end
end
function engine:debug(enable)
self._debug = enable;
if enable then
engine.insert = debugwrap("insert", engine.execute_update);
engine.select = debugwrap("select", engine.execute_query);
engine.delete = debugwrap("delete", engine.execute_update);
engine.update = debugwrap("update", engine.execute_update);
else
engine.insert = engine.execute_update;
engine.select = engine.execute_query;
engine.delete = engine.execute_update;
engine.update = engine.execute_update;
end
end
local function handleerr(err)
log("error", "Error in SQL transaction: %s", debug_traceback(err, 3));
return err;
end
function engine:_transaction(func, ...)
if not self.conn then
local ok, err = self:connect();
if not ok then return ok, err; end
end
--assert(not self.__transaction, "Recursive transactions not allowed");
local args, n_args = {...}, select("#", ...);
local function f() return func(unpack(args, 1, n_args)); end
log("debug", "SQL transaction begin [%s]", tostring(func));
self.__transaction = true;
local success, a, b, c = xpcall(f, handleerr);
self.__transaction = nil;
if success then
log("debug", "SQL transaction success [%s]", tostring(func));
local ok, err = self.conn:commit();
if not ok then return ok, err; end -- commit failed
return success, a, b, c;
else
log("debug", "SQL transaction failure [%s]: %s", tostring(func), a);
if self.conn then self.conn:rollback(); end
return success, a;
end
end
function engine:transaction(...)
local ok, ret = self:_transaction(...);
if not ok then
local conn = self.conn;
if not conn or not conn:ping() then
self.conn = nil;
ok, ret = self:_transaction(...);
end
end
return ok, ret;
end
function engine:_create_index(index)
local sql = "CREATE INDEX \""..index.name.."\" ON \""..index.table.."\" (";
for i=1,#index do
sql = sql.."\""..index[i].."\"";
if i ~= #index then sql = sql..", "; end
end
sql = sql..");"
if self.params.driver == "MySQL" then
sql = sql:gsub("\"([,)])", "\"(20)%1");
end
if index.unique then
sql = sql:gsub("^CREATE", "CREATE UNIQUE");
end
if self._debug then
debugquery("create", sql);
end
return self:execute(sql);
end
function engine:_create_table(table)
local sql = "CREATE TABLE \""..table.name.."\" (";
for i,col in ipairs(table.c) do
local col_type = col.type;
if col_type == "MEDIUMTEXT" and self.params.driver ~= "MySQL" then
col_type = "TEXT"; -- MEDIUMTEXT is MySQL-specific
end
if col.auto_increment == true and self.params.driver == "PostgreSQL" then
col_type = "BIGSERIAL";
end
sql = sql.."\""..col.name.."\" "..col_type;
if col.nullable == false then sql = sql.." NOT NULL"; end
if col.primary_key == true then sql = sql.." PRIMARY KEY"; end
if col.auto_increment == true then
if self.params.driver == "MySQL" then
sql = sql.." AUTO_INCREMENT";
elseif self.params.driver == "SQLite3" then
sql = sql.." AUTOINCREMENT";
end
end
if i ~= #table.c then sql = sql..", "; end
end
sql = sql.. ");"
if self.params.driver == "MySQL" then
sql = sql:gsub(";$", (" CHARACTER SET '%s' COLLATE '%s_bin';"):format(self.charset, self.charset));
end
if self._debug then
debugquery("create", sql);
end
local success,err = self:execute(sql);
if not success then return success,err; end
for _, v in ipairs(table.__table__) do
if is_index(v) then
self:_create_index(v);
end
end
return success;
end
function engine:set_encoding() -- to UTF-8
local driver = self.params.driver;
if driver == "SQLite3" then
return self:transaction(function()
for encoding in self:select"PRAGMA encoding;" do
if encoding[1] == "UTF-8" then
self.charset = "utf8";
end
end
end);
end
local set_names_query = "SET NAMES '%s';"
local charset = "utf8";
if driver == "MySQL" then
self:transaction(function()
for row in self:select"SELECT \"CHARACTER_SET_NAME\" FROM \"information_schema\".\"CHARACTER_SETS\" WHERE \"CHARACTER_SET_NAME\" LIKE 'utf8%' ORDER BY MAXLEN DESC LIMIT 1;" do
charset = row and row[1] or charset;
end
end);
set_names_query = set_names_query:gsub(";$", (" COLLATE '%s';"):format(charset.."_bin"));
end
self.charset = charset;
log("debug", "Using encoding '%s' for database connection", charset);
local ok, err = self:transaction(function() return self:execute(set_names_query:format(charset)); end);
if not ok then
return ok, err;
end
if driver == "MySQL" then
local ok, actual_charset = self:transaction(function ()
return self:select"SHOW SESSION VARIABLES LIKE 'character_set_client'";
end);
local charset_ok = true;
for row in actual_charset do
if row[2] ~= charset then
log("error", "MySQL %s is actually %q (expected %q)", row[1], row[2], charset);
charset_ok = false;
end
end
if not charset_ok then
return false, "Failed to set connection encoding";
end
end
return true;
end
local engine_mt = { __index = engine };
local function db2uri(params)
return build_url{
scheme = params.driver,
user = params.username,
password = params.password,
host = params.host,
port = params.port,
path = params.database,
};
end
local function create_engine(self, params, onconnect)
return setmetatable({ url = db2uri(params), params = params, onconnect = onconnect }, engine_mt);
end
return {
is_column = is_column;
is_index = is_index;
is_table = is_table;
is_query = is_query;
Integer = Integer;
String = String;
Column = Column;
Table = Table;
Index = Index;
create_engine = create_engine;
db2uri = db2uri;
};
prosody-0.10.0/util/datetime.lua 0000644 0001750 0001750 00000003050 13163172043 016445 0 ustar matthew matthew -- 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.
--
-- XEP-0082: XMPP Date and Time Profiles
local os_date = os.date;
local os_time = os.time;
local os_difftime = os.difftime;
local tonumber = tonumber;
local _ENV = nil;
local function date(t)
return os_date("!%Y-%m-%d", t);
end
local function datetime(t)
return os_date("!%Y-%m-%dT%H:%M:%SZ", t);
end
local function time(t)
return os_date("!%H:%M:%S", t);
end
local function legacy(t)
return os_date("!%Y%m%dT%H:%M:%S", t);
end
local function parse(s)
if s then
local year, month, day, hour, min, sec, tzd;
year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$");
if year then
local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone
local tzd_offset = 0;
if tzd ~= "" and tzd ~= "Z" then
local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)");
if not sign then return; end
if #m ~= 2 then m = "0"; end
h, m = tonumber(h), tonumber(m);
tzd_offset = h * 60 * 60 + m * 60;
if sign == "-" then tzd_offset = -tzd_offset; end
end
sec = (sec + time_offset) - tzd_offset;
return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false});
end
end
end
return {
date = date;
datetime = datetime;
time = time;
legacy = legacy;
parse = parse;
};
prosody-0.10.0/util/statistics.lua 0000644 0001750 0001750 00000010126 13163172043 017045 0 ustar matthew matthew local t_sort = table.sort
local m_floor = math.floor;
local time = require "util.time".now;
local function nop_function() end
local function percentile(arr, length, pc)
local n = pc/100 * (length + 1);
local k, d = m_floor(n), n%1;
if k == 0 then
return arr[1] or 0;
elseif k >= length then
return arr[length];
end
return arr[k] + d*(arr[k+1] - arr[k]);
end
local function new_registry(config)
config = config or {};
local duration_sample_interval = config.duration_sample_interval or 5;
local duration_max_samples = config.duration_max_stored_samples or 5000;
local function get_distribution_stats(events, n_actual_events, since, new_time, units)
local n_stored_events = #events;
t_sort(events);
local sum = 0;
for i = 1, n_stored_events do
sum = sum + events[i];
end
return {
samples = events;
sample_count = n_stored_events;
count = n_actual_events,
rate = n_actual_events/(new_time-since);
average = n_stored_events > 0 and sum/n_stored_events or 0,
min = events[1] or 0,
max = events[n_stored_events] or 0,
units = units,
};
end
local registry = {};
local methods;
methods = {
amount = function (name, initial)
local v = initial or 0;
registry[name..":amount"] = function () return "amount", v; end
return function (new_v) v = new_v; end
end;
counter = function (name, initial)
local v = initial or 0;
registry[name..":amount"] = function () return "amount", v; end
return function (delta)
v = v + delta;
end;
end;
rate = function (name)
local since, n = time(), 0;
registry[name..":rate"] = function ()
local t = time();
local stats = {
rate = n/(t-since);
count = n;
};
since, n = t, 0;
return "rate", stats.rate, stats;
end;
return function ()
n = n + 1;
end;
end;
distribution = function (name, unit, type)
type = type or "distribution";
local events, last_event = {}, 0;
local n_actual_events = 0;
local since = time();
registry[name..":"..type] = function ()
local new_time = time();
local stats = get_distribution_stats(events, n_actual_events, since, new_time, unit);
events, last_event = {}, 0;
n_actual_events = 0;
since = new_time;
return type, stats.average, stats;
end;
return function (value)
n_actual_events = n_actual_events + 1;
if n_actual_events%duration_sample_interval == 1 then
last_event = (last_event%duration_max_samples) + 1;
events[last_event] = value;
end
end;
end;
sizes = function (name)
return methods.distribution(name, "bytes", "size");
end;
times = function (name)
local events, last_event = {}, 0;
local n_actual_events = 0;
local since = time();
registry[name..":duration"] = function ()
local new_time = time();
local stats = get_distribution_stats(events, n_actual_events, since, new_time, "seconds");
events, last_event = {}, 0;
n_actual_events = 0;
since = new_time;
return "duration", stats.average, stats;
end;
return function ()
n_actual_events = n_actual_events + 1;
if n_actual_events%duration_sample_interval ~= 1 then
return nop_function;
end
local start_time = time();
return function ()
local end_time = time();
local duration = end_time - start_time;
last_event = (last_event%duration_max_samples) + 1;
events[last_event] = duration;
end
end;
end;
get_stats = function ()
return registry;
end;
};
return methods;
end
return {
new = new_registry;
get_histogram = function (duration, n_buckets)
n_buckets = n_buckets or 100;
local events, n_events = duration.samples, duration.sample_count;
if not (events and n_events) then
return nil, "not a valid distribution stat";
end
local histogram = {};
for i = 1, 100, 100/n_buckets do
histogram[i] = percentile(events, n_events, i);
end
return histogram;
end;
get_percentile = function (duration, pc)
local events, n_events = duration.samples, duration.sample_count;
if not (events and n_events) then
return nil, "not a valid distribution stat";
end
return percentile(events, n_events, pc);
end;
}
prosody-0.10.0/util/rsm.lua 0000644 0001750 0001750 00000004101 13163172043 015450 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2011-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- XEP-0313: Message Archive Management for Prosody
--
local stanza = require"util.stanza".stanza;
local tostring, tonumber = tostring, tonumber;
local type = type;
local pairs = pairs;
local xmlns_rsm = 'http://jabber.org/protocol/rsm';
local element_parsers = {};
do
local parsers = element_parsers;
local function xs_int(st)
return tonumber((st:get_text()));
end
local function xs_string(st)
return st:get_text();
end
parsers.after = xs_string;
parsers.before = function(st)
local text = st:get_text();
return text == "" or text;
end;
parsers.max = xs_int;
parsers.index = xs_int;
parsers.first = function(st)
return { index = tonumber(st.attr.index); st:get_text() };
end;
parsers.last = xs_string;
parsers.count = xs_int;
end
local element_generators = setmetatable({
first = function(st, data)
if type(data) == "table" then
st:tag("first", { index = data.index }):text(data[1]):up();
else
st:tag("first"):text(tostring(data)):up();
end
end;
before = function(st, data)
if data == true then
st:tag("before"):up();
else
st:tag("before"):text(tostring(data)):up();
end
end
}, {
__index = function(_, name)
return function(st, data)
st:tag(name):text(tostring(data)):up();
end
end;
});
local function parse(set)
local rs = {};
for tag in set:childtags() do
local name = tag.name;
local parser = name and element_parsers[name];
if parser then
rs[name] = parser(tag);
end
end
return rs;
end
local function generate(t)
local st = stanza("set", { xmlns = xmlns_rsm });
for k,v in pairs(t) do
if element_parsers[k] then
element_generators[k](st, v);
end
end
return st;
end
local function get(st)
local set = st:get_child("set", xmlns_rsm);
if set and #set.tags > 0 then
return parse(set);
end
end
return { parse = parse, generate = generate, get = get };
prosody-0.10.0/util/timer.lua 0000644 0001750 0001750 00000003467 13163172043 016005 0 ustar matthew matthew -- 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 server = require "net.server";
local math_min = math.min
local math_huge = math.huge
local get_time = require "util.time".now
local t_insert = table.insert;
local pairs = pairs;
local type = type;
local data = {};
local new_data = {};
local _ENV = nil;
local _add_task;
if not server.event then
function _add_task(delay, callback)
local current_time = get_time();
delay = delay + current_time;
if delay >= current_time then
t_insert(new_data, {delay, callback});
else
local r = callback(current_time);
if r and type(r) == "number" then
return _add_task(r, callback);
end
end
end
server._addtimer(function()
local current_time = get_time();
if #new_data > 0 then
for _, d in pairs(new_data) do
t_insert(data, d);
end
new_data = {};
end
local next_time = math_huge;
for i, d in pairs(data) do
local t, callback = d[1], d[2];
if t <= current_time then
data[i] = nil;
local r = callback(current_time);
if type(r) == "number" then
_add_task(r, callback);
next_time = math_min(next_time, r);
end
else
next_time = math_min(next_time, t - current_time);
end
end
return next_time;
end);
else
local event = server.event;
local event_base = server.event_base;
local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1;
function _add_task(delay, callback)
local event_handle;
event_handle = event_base:addevent(nil, 0, function ()
local ret = callback(get_time());
if ret then
return 0, ret;
elseif event_handle then
return EVENT_LEAVE;
end
end
, delay);
end
end
return {
add_task = _add_task;
};
prosody-0.10.0/util/mercurial.lua 0000644 0001750 0001750 00000001713 13163172043 016640 0 ustar matthew matthew
local lfs = require"lfs";
local hg = { };
function hg.check_id(path)
if lfs.attributes(path, 'mode') ~= "directory" then
return nil, "not a directory";
end
local hg_dirstate = io.open(path.."/.hg/dirstate");
local hgid, hgrepo
if hg_dirstate then
hgid = ("%02x%02x%02x%02x%02x%02x"):format(hg_dirstate:read(6):byte(1, 6));
hg_dirstate:close();
local hg_changelog = io.open(path.."/.hg/store/00changelog.i");
if hg_changelog then
hg_changelog:seek("set", 0x20);
hgrepo = ("%02x%02x%02x%02x%02x%02x"):format(hg_changelog:read(6):byte(1, 6));
hg_changelog:close();
end
else
local hg_archival,e = io.open(path.."/.hg_archival.txt");
if hg_archival then
local repo = hg_archival:read("*l");
local node = hg_archival:read("*l");
hg_archival:close()
hgid = node and node:match("^node: (%x%x%x%x%x%x%x%x%x%x%x%x)")
hgrepo = repo and repo:match("^repo: (%x%x%x%x%x%x%x%x%x%x%x%x)")
end
end
return hgid, hgrepo;
end
return hg;
prosody-0.10.0/util/format.lua 0000644 0001750 0001750 00000003420 13163172043 016142 0 ustar matthew matthew --
-- A string.format wrapper that gracefully handles invalid arguments
--
local tostring = tostring;
local select = select;
local assert = assert;
local unpack = unpack;
local type = type;
local function format(format, ...)
local args, args_length = { ... }, select('#', ...);
-- format specifier spec:
-- 1. Start: '%%'
-- 2. Flags: '[%-%+ #0]'
-- 3. Width: '%d?%d?'
-- 4. Precision: '%.?%d?%d?'
-- 5. Option: '[cdiouxXaAeEfgGqs%%]'
--
-- The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, whereas q and s expect a string.
-- This function does not accept string values containing embedded zeros, except as arguments to the q option.
-- a and A are only in Lua 5.2+
-- process each format specifier
local i = 0;
format = format:gsub("%%[^cdiouxXaAeEfgGqs%%]*[cdiouxXaAeEfgGqs%%]", function(spec)
if spec ~= "%%" then
i = i + 1;
local arg = args[i];
if arg == nil then -- special handling for nil
arg = ""
args[i] = "";
end
local option = spec:sub(-1);
if option == "q" or option == "s" then -- arg should be string
args[i] = tostring(arg);
elseif type(arg) ~= "number" then -- arg isn't number as expected?
args[i] = tostring(arg);
spec = "[%s]";
end
end
return spec;
end);
-- process extra args
while i < args_length do
i = i + 1;
local arg = args[i];
if arg == nil then
args[i] = "";
else
args[i] = tostring(arg);
end
format = format .. " [%s]"
end
return format:format(unpack(args));
end
local function test()
assert(format("%s", "hello") == "hello");
assert(format("%s") == "");
assert(format("%s", true) == "true");
assert(format("%d", true) == "[true]");
assert(format("%%", true) == "% [true]");
end
return {
format = format;
test = test;
};
prosody-0.10.0/util/json.lua 0000644 0001750 0001750 00000023627 13163172043 015636 0 ustar matthew matthew -- 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 t_insert, t_concat, t_remove, t_sort = table.insert, table.concat, table.remove, table.sort;
local s_char = string.char;
local tostring, tonumber = tostring, tonumber;
local pairs, ipairs = pairs, ipairs;
local next = next;
local getmetatable, setmetatable = getmetatable, setmetatable;
local print = print;
local has_array, array = pcall(require, "util.array");
local array_mt = has_array and getmetatable(array()) or {};
--module("json")
local module = {};
local null = setmetatable({}, { __tostring = function() return "null"; end; });
module.null = null;
local escapes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b",
["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"};
local unescapes = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/",
b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"};
for i=0,31 do
local ch = s_char(i);
if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end
end
local function codepoint_to_utf8(code)
if code < 0x80 then return s_char(code); end
local bits0_6 = code % 64;
if code < 0x800 then
local bits6_5 = (code - bits0_6) / 64;
return s_char(0x80 + 0x40 + bits6_5, 0x80 + bits0_6);
end
local bits0_12 = code % 4096;
local bits6_6 = (bits0_12 - bits0_6) / 64;
local bits12_4 = (code - bits0_12) / 4096;
return s_char(0x80 + 0x40 + 0x20 + bits12_4, 0x80 + bits6_6, 0x80 + bits0_6);
end
local valid_types = {
number = true,
string = true,
table = true,
boolean = true
};
local special_keys = {
__array = true;
__hash = true;
};
local simplesave, tablesave, arraysave, stringsave;
function stringsave(o, buffer)
-- FIXME do proper utf-8 and binary data detection
t_insert(buffer, "\""..(o:gsub(".", escapes)).."\"");
end
function arraysave(o, buffer)
t_insert(buffer, "[");
if next(o) then
for _, v in ipairs(o) do
simplesave(v, buffer);
t_insert(buffer, ",");
end
t_remove(buffer);
end
t_insert(buffer, "]");
end
function tablesave(o, buffer)
local __array = {};
local __hash = {};
local hash = {};
for i,v in ipairs(o) do
__array[i] = v;
end
for k,v in pairs(o) do
local ktype, vtype = type(k), type(v);
if valid_types[vtype] or v == null then
if ktype == "string" and not special_keys[k] then
hash[k] = v;
elseif (valid_types[ktype] or k == null) and __array[k] == nil then
__hash[k] = v;
end
end
end
if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then
t_insert(buffer, "{");
local mark = #buffer;
if buffer.ordered then
local keys = {};
for k in pairs(hash) do
t_insert(keys, k);
end
t_sort(keys);
for _,k in ipairs(keys) do
stringsave(k, buffer);
t_insert(buffer, ":");
simplesave(hash[k], buffer);
t_insert(buffer, ",");
end
else
for k,v in pairs(hash) do
stringsave(k, buffer);
t_insert(buffer, ":");
simplesave(v, buffer);
t_insert(buffer, ",");
end
end
if next(__hash) ~= nil then
t_insert(buffer, "\"__hash\":[");
for k,v in pairs(__hash) do
simplesave(k, buffer);
t_insert(buffer, ",");
simplesave(v, buffer);
t_insert(buffer, ",");
end
t_remove(buffer);
t_insert(buffer, "]");
t_insert(buffer, ",");
end
if next(__array) then
t_insert(buffer, "\"__array\":");
arraysave(__array, buffer);
t_insert(buffer, ",");
end
if mark ~= #buffer then t_remove(buffer); end
t_insert(buffer, "}");
else
arraysave(__array, buffer);
end
end
function simplesave(o, buffer)
local t = type(o);
if o == null then
t_insert(buffer, "null");
elseif t == "number" then
t_insert(buffer, tostring(o));
elseif t == "string" then
stringsave(o, buffer);
elseif t == "table" then
local mt = getmetatable(o);
if mt == array_mt then
arraysave(o, buffer);
else
tablesave(o, buffer);
end
elseif t == "boolean" then
t_insert(buffer, (o and "true" or "false"));
else
t_insert(buffer, "null");
end
end
function module.encode(obj)
local t = {};
simplesave(obj, t);
return t_concat(t);
end
function module.encode_ordered(obj)
local t = { ordered = true };
simplesave(obj, t);
return t_concat(t);
end
function module.encode_array(obj)
local t = {};
arraysave(obj, t);
return t_concat(t);
end
-----------------------------------
local function _skip_whitespace(json, index)
return json:find("[^ \t\r\n]", index) or index; -- no need to check \r\n, we converted those to \t
end
local function _fixobject(obj)
local __array = obj.__array;
if __array then
obj.__array = nil;
for _, v in ipairs(__array) do
t_insert(obj, v);
end
end
local __hash = obj.__hash;
if __hash then
obj.__hash = nil;
local k;
for _, v in ipairs(__hash) do
if k ~= nil then
obj[k] = v; k = nil;
else
k = v;
end
end
end
return obj;
end
local _readvalue, _readstring;
local function _readobject(json, index)
local o = {};
while true do
local key, val;
index = _skip_whitespace(json, index + 1);
if json:byte(index) ~= 0x22 then -- "\""
if json:byte(index) == 0x7d then return o, index + 1; end -- "}"
return nil, "key expected";
end
key, index = _readstring(json, index);
if key == nil then return nil, index; end
index = _skip_whitespace(json, index);
if json:byte(index) ~= 0x3a then return nil, "colon expected"; end -- ":"
val, index = _readvalue(json, index + 1);
if val == nil then return nil, index; end
o[key] = val;
index = _skip_whitespace(json, index);
local b = json:byte(index);
if b == 0x7d then return _fixobject(o), index + 1; end -- "}"
if b ~= 0x2c then return nil, "object eof"; end -- ","
end
end
local function _readarray(json, index)
local a = {};
local oindex = index;
while true do
local val;
val, index = _readvalue(json, index + 1);
if val == nil then
if json:byte(oindex + 1) == 0x5d then return setmetatable(a, array_mt), oindex + 2; end -- "]"
return val, index;
end
t_insert(a, val);
index = _skip_whitespace(json, index);
local b = json:byte(index);
if b == 0x5d then return setmetatable(a, array_mt), index + 1; end -- "]"
if b ~= 0x2c then return nil, "array eof"; end -- ","
end
end
local _unescape_error;
local function _unescape_surrogate_func(x)
local lead, trail = tonumber(x:sub(3, 6), 16), tonumber(x:sub(9, 12), 16);
local codepoint = lead * 0x400 + trail - 0x35FDC00;
local a = codepoint % 64;
codepoint = (codepoint - a) / 64;
local b = codepoint % 64;
codepoint = (codepoint - b) / 64;
local c = codepoint % 64;
codepoint = (codepoint - c) / 64;
return s_char(0xF0 + codepoint, 0x80 + c, 0x80 + b, 0x80 + a);
end
local function _unescape_func(x)
x = x:match("%x%x%x%x", 3);
if x then
--if x >= 0xD800 and x <= 0xDFFF then _unescape_error = true; end -- bad surrogate pair
return codepoint_to_utf8(tonumber(x, 16));
end
_unescape_error = true;
end
function _readstring(json, index)
index = index + 1;
local endindex = json:find("\"", index, true);
if endindex then
local s = json:sub(index, endindex - 1);
--if s:find("[%z-\31]") then return nil, "control char in string"; end
-- FIXME handle control characters
_unescape_error = nil;
--s = s:gsub("\\u[dD][89abAB]%x%x\\u[dD][cdefCDEF]%x%x", _unescape_surrogate_func);
-- FIXME handle escapes beyond BMP
s = s:gsub("\\u.?.?.?.?", _unescape_func);
if _unescape_error then return nil, "invalid escape"; end
return s, endindex + 1;
end
return nil, "string eof";
end
local function _readnumber(json, index)
local m = json:match("[0-9%.%-eE%+]+", index); -- FIXME do strict checking
return tonumber(m), index + #m;
end
local function _readnull(json, index)
local a, b, c = json:byte(index + 1, index + 3);
if a == 0x75 and b == 0x6c and c == 0x6c then
return null, index + 4;
end
return nil, "null parse failed";
end
local function _readtrue(json, index)
local a, b, c = json:byte(index + 1, index + 3);
if a == 0x72 and b == 0x75 and c == 0x65 then
return true, index + 4;
end
return nil, "true parse failed";
end
local function _readfalse(json, index)
local a, b, c, d = json:byte(index + 1, index + 4);
if a == 0x61 and b == 0x6c and c == 0x73 and d == 0x65 then
return false, index + 5;
end
return nil, "false parse failed";
end
function _readvalue(json, index)
index = _skip_whitespace(json, index);
local b = json:byte(index);
-- TODO try table lookup instead of if-else?
if b == 0x7B then -- "{"
return _readobject(json, index);
elseif b == 0x5B then -- "["
return _readarray(json, index);
elseif b == 0x22 then -- "\""
return _readstring(json, index);
elseif b ~= nil and b >= 0x30 and b <= 0x39 or b == 0x2d then -- "0"-"9" or "-"
return _readnumber(json, index);
elseif b == 0x6e then -- "n"
return _readnull(json, index);
elseif b == 0x74 then -- "t"
return _readtrue(json, index);
elseif b == 0x66 then -- "f"
return _readfalse(json, index);
else
return nil, "value expected";
end
end
local first_escape = {
["\\\""] = "\\u0022";
["\\\\"] = "\\u005c";
["\\/" ] = "\\u002f";
["\\b" ] = "\\u0008";
["\\f" ] = "\\u000C";
["\\n" ] = "\\u000A";
["\\r" ] = "\\u000D";
["\\t" ] = "\\u0009";
["\\u" ] = "\\u";
};
function module.decode(json)
json = json:gsub("\\.", first_escape) -- get rid of all escapes except \uXXXX, making string parsing much simpler
--:gsub("[\r\n]", "\t"); -- \r\n\t are equivalent, we care about none of them, and none of them can be in strings
-- TODO do encoding verification
local val, index = _readvalue(json, 1);
if val == nil then return val, index; end
if json:find("[^ \t\r\n]", index) then return nil, "garbage at eof"; end
return val;
end
function module.test(object)
local encoded = module.encode(object);
local decoded = module.decode(encoded);
local recoded = module.encode(decoded);
if encoded ~= recoded then
print("FAILED");
print("encoded:", encoded);
print("recoded:", recoded);
else
print(encoded);
end
return encoded == recoded;
end
return module;
prosody-0.10.0/util/dependencies.lua 0000644 0001750 0001750 00000014324 13163172043 017305 0 ustar matthew matthew -- 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 function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end
-- Required to be able to find packages installed with luarocks
if not softreq "luarocks.loader" then -- LuaRocks 2.x
softreq "luarocks.require"; -- LuaRocks <1.x
end
local function missingdep(name, sources, msg)
print("");
print("**************************");
print("Prosody was unable to find "..tostring(name));
print("This package can be obtained in the following ways:");
print("");
local longest_platform = 0;
for platform in pairs(sources) do
longest_platform = math.max(longest_platform, #platform);
end
for platform, source in pairs(sources) do
print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source);
end
print("");
print(msg or (name.." is required for Prosody to run, so we will now exit."));
print("More help can be found on our website, at http://prosody.im/doc/depends");
print("**************************");
print("");
end
-- COMPAT w/pre-0.8 Debian: The Debian config file used to use
-- util.ztact, which has been removed from Prosody in 0.8. This
-- is to log an error for people who still use it, so they can
-- update their configs.
package.preload["util.ztact"] = function ()
if not package.loaded["core.loggingmanager"] then
error("util.ztact has been removed from Prosody and you need to fix your config "
.."file. More information can be found at http://prosody.im/doc/packagers#ztact", 0);
else
error("module 'util.ztact' has been deprecated in Prosody 0.8.");
end
end;
local function check_dependencies()
if _VERSION < "Lua 5.1" then
print "***********************************"
print("Unsupported Lua version: ".._VERSION);
print("At least Lua 5.1 is required.");
print "***********************************"
return false;
end
local fatal;
local lxp = softreq "lxp"
if not lxp then
missingdep("luaexpat", {
["Debian/Ubuntu"] = "sudo apt-get install lua-expat";
["luarocks"] = "luarocks install luaexpat";
["Source"] = "http://matthewwild.co.uk/projects/luaexpat/";
});
fatal = true;
end
local socket = softreq "socket"
if not socket then
missingdep("luasocket", {
["Debian/Ubuntu"] = "sudo apt-get install lua-socket";
["luarocks"] = "luarocks install luasocket";
["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/";
});
fatal = true;
end
local lfs, err = softreq "lfs"
if not lfs then
missingdep("luafilesystem", {
["luarocks"] = "luarocks install luafilesystem";
["Debian/Ubuntu"] = "sudo apt-get install lua-filesystem";
["Source"] = "http://www.keplerproject.org/luafilesystem/";
});
fatal = true;
end
local ssl = softreq "ssl"
if not ssl then
missingdep("LuaSec", {
["Debian/Ubuntu"] = "sudo apt-get install lua-sec";
["luarocks"] = "luarocks install luasec";
["Source"] = "https://github.com/brunoos/luasec";
}, "SSL/TLS support will not be available");
end
local bit = _G.bit32 or softreq"bit";
if not bit then
missingdep("lua-bitops", {
["Debian/Ubuntu"] = "sudo apt-get install lua-bitop";
["luarocks"] = "luarocks install luabitop";
["Source"] = "http://bitop.luajit.org/";
}, "WebSocket support will not be available");
end
local encodings, err = softreq "util.encodings"
if not encodings then
if err:match("module '[^']*' not found") then
missingdep("util.encodings", {
["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/";
["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so";
});
else
print "***********************************"
print("util/encodings couldn't be loaded. Check that you have a recent version of libidn");
print ""
print("The full error was:");
print(err)
print "***********************************"
end
fatal = true;
end
local hashes, err = softreq "util.hashes"
if not hashes then
if err:match("module '[^']*' not found") then
missingdep("util.hashes", {
["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/";
["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so";
});
else
print "***********************************"
print("util/hashes couldn't be loaded. Check that you have a recent version of OpenSSL (libcrypto in particular)");
print ""
print("The full error was:");
print(err)
print "***********************************"
end
fatal = true;
end
return not fatal;
end
local function log_warnings()
if _VERSION > "Lua 5.2" then
prosody.log("warn", "Support for %s is experimental, please report any issues", _VERSION);
end
local ssl = softreq"ssl";
if ssl then
local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)");
if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then
prosody.log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
end
end
local lxp = softreq"lxp";
if lxp then
if not pcall(lxp.new, { StartDoctypeDecl = false }) then
prosody.log("error", "The version of LuaExpat on your system leaves Prosody "
.."vulnerable to denial-of-service attacks. You should upgrade to "
.."LuaExpat 1.3.0 or higher as soon as possible. See "
.."http://prosody.im/doc/depends#luaexpat for more information.");
end
if not lxp.new({}).getcurrentbytecount then
prosody.log("error", "The version of LuaExpat on your system does not support "
.."stanza size limits, which may leave servers on untrusted "
.."networks (e.g. the internet) vulnerable to denial-of-service "
.."attacks. You should upgrade to LuaExpat 1.3.0 or higher as "
.."soon as possible. See "
.."http://prosody.im/doc/depends#luaexpat for more information.");
end
end
end
return {
softreq = softreq;
missingdep = missingdep;
check_dependencies = check_dependencies;
log_warnings = log_warnings;
};
prosody-0.10.0/util/time.lua 0000644 0001750 0001750 00000000302 13163172043 015604 0 ustar matthew matthew -- Import gettime() from LuaSocket, as a way to access high-resolution time
-- in a platform-independent way
local socket_gettime = require "socket".gettime;
return {
now = socket_gettime;
}
prosody-0.10.0/util/openssl.lua 0000644 0001750 0001750 00000011007 13163172043 016335 0 ustar matthew matthew local type, tostring, pairs, ipairs = type, tostring, pairs, ipairs;
local t_insert, t_concat = table.insert, table.concat;
local s_format = string.format;
local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE]
local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID]
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local _M = {};
local config = {};
_M.config = config;
local ssl_config = {};
local ssl_config_mt = { __index = ssl_config };
function config.new()
return setmetatable({
req = {
distinguished_name = "distinguished_name",
req_extensions = "certrequest",
x509_extensions = "selfsigned",
prompt = "no",
},
distinguished_name = {
countryName = "GB",
-- stateOrProvinceName = "",
localityName = "The Internet",
organizationName = "Your Organisation",
organizationalUnitName = "XMPP Department",
commonName = "example.com",
emailAddress = "xmpp@example.com",
},
certrequest = {
basicConstraints = "CA:FALSE",
keyUsage = "digitalSignature,keyEncipherment",
extendedKeyUsage = "serverAuth,clientAuth",
subjectAltName = "@subject_alternative_name",
},
selfsigned = {
basicConstraints = "CA:TRUE",
subjectAltName = "@subject_alternative_name",
},
subject_alternative_name = {
DNS = {},
otherName = {},
},
}, ssl_config_mt);
end
local DN_order = {
"countryName";
"stateOrProvinceName";
"localityName";
"streetAddress";
"organizationName";
"organizationalUnitName";
"commonName";
"emailAddress";
}
_M._DN_order = DN_order;
function ssl_config:serialize()
local s = "";
for section, t in pairs(self) do
s = s .. ("[%s]\n"):format(section);
if section == "subject_alternative_name" then
for san, n in pairs(t) do
for i = 1, #n do
s = s .. s_format("%s.%d = %s\n", san, i -1, n[i]);
end
end
elseif section == "distinguished_name" then
for _, k in ipairs(t[1] and t or DN_order) do
local v = t[k];
if v then
s = s .. ("%s = %s\n"):format(k, v);
end
end
else
for k, v in pairs(t) do
s = s .. ("%s = %s\n"):format(k, v);
end
end
s = s .. "\n";
end
return s;
end
local function utf8string(s)
-- This is how we tell openssl not to encode UTF-8 strings as fake Latin1
return s_format("FORMAT:UTF8,UTF8:%s", s);
end
local function ia5string(s)
return s_format("IA5STRING:%s", s);
end
_M.util = {
utf8string = utf8string,
ia5string = ia5string,
};
function ssl_config:add_dNSName(host)
t_insert(self.subject_alternative_name.DNS, idna_to_ascii(host));
end
function ssl_config:add_sRVName(host, service)
t_insert(self.subject_alternative_name.otherName,
s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .. "." .. idna_to_ascii(host))));
end
function ssl_config:add_xmppAddr(host)
t_insert(self.subject_alternative_name.otherName,
s_format("%s;%s", oid_xmppaddr, utf8string(host)));
end
function ssl_config:from_prosody(hosts, config, certhosts)
-- TODO Decide if this should go elsewhere
local found_matching_hosts = false;
for i = 1, #certhosts do
local certhost = certhosts[i];
for name in pairs(hosts) do
if name == certhost or name:sub(-1-#certhost) == "." .. certhost then
found_matching_hosts = true;
self:add_dNSName(name);
--print(name .. "#component_module: " .. (config.get(name, "component_module") or "nil"));
if config.get(name, "component_module") == nil then
self:add_sRVName(name, "xmpp-client");
end
--print(name .. "#anonymous_login: " .. tostring(config.get(name, "anonymous_login")));
if not (config.get(name, "anonymous_login") or
config.get(name, "authentication") == "anonymous") then
self:add_sRVName(name, "xmpp-server");
end
self:add_xmppAddr(name);
end
end
end
if not found_matching_hosts then
return nil, "no-matching-hosts";
end
end
do -- Lua to shell calls.
local function shell_escape(s)
return "'" .. tostring(s):gsub("'",[['\'']]) .. "'";
end
local function serialize(command, args)
local commandline = { "openssl", command };
for k, v in pairs(args) do
if type(k) == "string" then
t_insert(commandline, ("-%s"):format(k));
if v ~= true then
t_insert(commandline, shell_escape(v));
end
end
end
for _, v in ipairs(args) do
t_insert(commandline, shell_escape(v));
end
return t_concat(commandline, " ");
end
local os_execute = os.execute;
setmetatable(_M, {
__index = function(_, command)
return function(opts)
local ret = os_execute(serialize(command, type(opts) == "table" and opts or {}));
return ret == true or ret == 0;
end;
end;
});
end
return _M;
prosody-0.10.0/util/session.lua 0000644 0001750 0001750 00000002534 13163172043 016342 0 ustar matthew matthew local initialize_filters = require "util.filters".initialize;
local logger = require "util.logger";
local function new_session(typ)
local session = {
type = typ .. "_unauthed";
};
return session;
end
local function set_id(session)
local id = session.type .. tostring(session):match("%x+$"):lower();
session.id = id;
return session;
end
local function set_logger(session)
local log = logger.init(session.id);
session.log = log;
return session;
end
local function set_conn(session, conn)
session.conn = conn;
session.ip = conn:ip();
return session;
end
local function set_send(session)
local conn = session.conn;
if not conn then
function session.send(data)
session.log("debug", "Discarding data sent to unconnected session: %s", tostring(data));
return false;
end
return session;
end
local filter = initialize_filters(session);
local w = conn.write;
session.send = function (t)
if t.name then
t = filter("stanzas/out", t);
end
if t then
t = filter("bytes/out", tostring(t));
if t then
local ret, err = w(conn, t);
if not ret then
session.log("debug", "Error writing to connection: %s", tostring(err));
return false, err;
end
end
end
return true;
end
return session;
end
return {
new = new_session;
set_id = set_id;
set_logger = set_logger;
set_conn = set_conn;
set_send = set_send;
}
prosody-0.10.0/util/rfc6724.lua 0000644 0001750 0001750 00000007200 13163172043 015747 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2011-2013 Florian Zeitz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- This is used to sort destination addresses by preference
-- during S2S connections.
-- We can't hand this off to getaddrinfo, since it blocks
local ip_commonPrefixLength = require"util.ip".commonPrefixLength
local function commonPrefixLength(ipA, ipB)
local len = ip_commonPrefixLength(ipA, ipB);
return len < 64 and len or 64;
end
local function t_sort(t, comp)
for i = 1, (#t - 1) do
for j = (i + 1), #t do
local a, b = t[i], t[j];
if not comp(a,b) then
t[i], t[j] = b, a;
end
end
end
end
local function source(dest, candidates)
local function comp(ipA, ipB)
-- Rule 1: Prefer same address
if dest == ipA then
return true;
elseif dest == ipB then
return false;
end
-- Rule 2: Prefer appropriate scope
if ipA.scope < ipB.scope then
if ipA.scope < dest.scope then
return false;
else
return true;
end
elseif ipA.scope > ipB.scope then
if ipB.scope < dest.scope then
return true;
else
return false;
end
end
-- Rule 3: Avoid deprecated addresses
-- XXX: No way to determine this
-- Rule 4: Prefer home addresses
-- XXX: Mobility Address related, no way to determine this
-- Rule 5: Prefer outgoing interface
-- XXX: Interface to address relation. No way to determine this
-- Rule 6: Prefer matching label
if ipA.label == dest.label and ipB.label ~= dest.label then
return true;
elseif ipB.label == dest.label and ipA.label ~= dest.label then
return false;
end
-- Rule 7: Prefer temporary addresses (over public ones)
-- XXX: No way to determine this
-- Rule 8: Use longest matching prefix
if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
return true;
else
return false;
end
end
t_sort(candidates, comp);
return candidates[1];
end
local function destination(candidates, sources)
local sourceAddrs = {};
local function comp(ipA, ipB)
local ipAsource = sourceAddrs[ipA];
local ipBsource = sourceAddrs[ipB];
-- Rule 1: Avoid unusable destinations
-- XXX: No such information
-- Rule 2: Prefer matching scope
if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
return true;
elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
return false;
end
-- Rule 3: Avoid deprecated addresses
-- XXX: No way to determine this
-- Rule 4: Prefer home addresses
-- XXX: Mobility Address related, no way to determine this
-- Rule 5: Prefer matching label
if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
return true;
elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
return false;
end
-- Rule 6: Prefer higher precedence
if ipA.precedence > ipB.precedence then
return true;
elseif ipA.precedence < ipB.precedence then
return false;
end
-- Rule 7: Prefer native transport
-- XXX: No way to determine this
-- Rule 8: Prefer smaller scope
if ipA.scope < ipB.scope then
return true;
elseif ipA.scope > ipB.scope then
return false;
end
-- Rule 9: Use longest matching prefix
if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
return true;
elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
return false;
end
-- Rule 10: Otherwise, leave order unchanged
return true;
end
for _, ip in ipairs(candidates) do
sourceAddrs[ip] = source(ip, sources);
end
t_sort(candidates, comp);
return candidates;
end
return {source = source,
destination = destination};
prosody-0.10.0/util/random.lua 0000644 0001750 0001750 00000001242 13163172043 016132 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2014 Matthew Wild
-- Copyright (C) 2008-2014 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local ok, crand = pcall(require, "util.crand");
if ok then return crand; end
local urandom, urandom_err = io.open("/dev/urandom", "r");
local function seed()
end
local function bytes(n)
return urandom:read(n);
end
if not urandom then
function bytes()
error("Unable to obtain a secure random number generator, please see https://prosody.im/doc/random ("..urandom_err..")");
end
end
return {
seed = seed;
bytes = bytes;
_source = "/dev/urandom";
};
prosody-0.10.0/util/set.lua 0000644 0001750 0001750 00000006017 13163172043 015452 0 ustar matthew matthew -- 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 ipairs, pairs, setmetatable, next, tostring =
ipairs, pairs, setmetatable, next, tostring;
local t_concat = table.concat;
local _ENV = nil;
local set_mt = {};
function set_mt.__call(set, _, k)
return next(set._items, k);
end
local items_mt = {};
function items_mt.__call(items, _, k)
return next(items, k);
end
local function new(list)
local items = setmetatable({}, items_mt);
local set = { _items = items };
-- We access the set through an upvalue in these methods, so ignore 'self' being unused
--luacheck: ignore 212/self
function set:add(item)
items[item] = true;
end
function set:contains(item)
return items[item];
end
function set:items()
return next, items;
end
function set:remove(item)
items[item] = nil;
end
function set:add_list(item_list)
if item_list then
for _, item in ipairs(item_list) do
items[item] = true;
end
end
end
function set:include(otherset)
for item in otherset do
items[item] = true;
end
end
function set:exclude(otherset)
for item in otherset do
items[item] = nil;
end
end
function set:empty()
return not next(items);
end
if list then
set:add_list(list);
end
return setmetatable(set, set_mt);
end
local function union(set1, set2)
local set = new();
local items = set._items;
for item in pairs(set1._items) do
items[item] = true;
end
for item in pairs(set2._items) do
items[item] = true;
end
return set;
end
local function difference(set1, set2)
local set = new();
local items = set._items;
for item in pairs(set1._items) do
items[item] = (not set2._items[item]) or nil;
end
return set;
end
local function intersection(set1, set2)
local set = new();
local items = set._items;
set1, set2 = set1._items, set2._items;
for item in pairs(set1) do
items[item] = (not not set2[item]) or nil;
end
return set;
end
local function xor(set1, set2)
return union(set1, set2) - intersection(set1, set2);
end
function set_mt.__add(set1, set2)
return union(set1, set2);
end
function set_mt.__sub(set1, set2)
return difference(set1, set2);
end
function set_mt.__div(set, func)
local new_set = new();
local items, new_items = set._items, new_set._items;
for item in pairs(items) do
local new_item = func(item);
if new_item ~= nil then
new_items[new_item] = true;
end
end
return new_set;
end
function set_mt.__eq(set1, set2)
set1, set2 = set1._items, set2._items;
for item in pairs(set1) do
if not set2[item] then
return false;
end
end
for item in pairs(set2) do
if not set1[item] then
return false;
end
end
return true;
end
function set_mt.__tostring(set)
local s, items = { }, set._items;
for item in pairs(items) do
s[#s+1] = tostring(item);
end
return t_concat(s, ", ");
end
return {
new = new;
union = union;
difference = difference;
intersection = intersection;
xor = xor;
};
prosody-0.10.0/util/datamanager.lua 0000644 0001750 0001750 00000026666 13163172043 017137 0 ustar matthew matthew -- 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 format = string.format;
local setmetatable = setmetatable;
local ipairs = ipairs;
local char = string.char;
local pcall = pcall;
local log = require "util.logger".init("datamanager");
local io_open = io.open;
local os_remove = os.remove;
local os_rename = os.rename;
local tonumber = tonumber;
local next = next;
local type = type;
local t_insert = table.insert;
local t_concat = table.concat;
local envloadfile = require"util.envload".envloadfile;
local serialize = require "util.serialization".serialize;
local lfs = require "lfs";
-- Extract directory seperator from package.config (an undocumented string that comes with lua)
local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" )
local prosody = prosody;
local raw_mkdir = lfs.mkdir;
local atomic_append;
local ENOENT = 2;
pcall(function()
local pposix = require "util.pposix";
raw_mkdir = pposix.mkdir or raw_mkdir; -- Doesn't trample on umask
atomic_append = pposix.atomic_append;
ENOENT = pposix.ENOENT or ENOENT;
end);
local _ENV = nil;
---- utils -----
local encode, decode;
do
local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber(k, 16)); return t[k]; end });
decode = function (s)
return s and (s:gsub("%%(%x%x)", urlcodes));
end
encode = function (s)
return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end));
end
end
if not atomic_append then
function atomic_append(f, data)
local pos = f:seek();
if not f:write(data) or not f:flush() then
f:seek("set", pos);
f:write((" "):rep(#data));
f:flush();
return nil, "write-failed";
end
return true;
end
end
local _mkdir = {};
local function mkdir(path)
path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here
if not _mkdir[path] then
raw_mkdir(path);
_mkdir[path] = true;
end
return path;
end
local data_path = (prosody and prosody.paths and prosody.paths.data) or ".";
local callbacks = {};
------- API -------------
local function set_data_path(path)
log("debug", "Setting data path to: %s", path);
data_path = path;
end
local function callback(username, host, datastore, data)
for _, f in ipairs(callbacks) do
username, host, datastore, data = f(username, host, datastore, data);
if username == false then break; end
end
return username, host, datastore, data;
end
local function add_callback(func)
if not callbacks[func] then -- Would you really want to set the same callback more than once?
callbacks[func] = true;
callbacks[#callbacks+1] = func;
return true;
end
end
local function remove_callback(func)
if callbacks[func] then
for i, f in ipairs(callbacks) do
if f == func then
callbacks[i] = nil;
callbacks[f] = nil;
return true;
end
end
end
end
local function getpath(username, host, datastore, ext, create)
ext = ext or "dat";
host = (host and encode(host)) or "_global";
username = username and encode(username);
if username then
if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end
return format("%s/%s/%s/%s.%s", data_path, host, datastore, username, ext);
else
if create then mkdir(mkdir(data_path).."/"..host); end
return format("%s/%s/%s.%s", data_path, host, datastore, ext);
end
end
local function load(username, host, datastore)
local data, err, errno = envloadfile(getpath(username, host, datastore), {});
if not data then
if errno == ENOENT then
-- No such file, ok to ignore
return nil;
end
log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, err, username or "nil", host or "nil");
return nil, "Error reading storage";
end
local success, ret = pcall(data);
if not success then
log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil");
return nil, "Error reading storage";
end
return ret;
end
local function atomic_store(filename, data)
local scratch = filename.."~";
local f, ok, msg, errno;
f, msg, errno = io_open(scratch, "w");
if not f then
return nil, msg;
end
ok, msg = f:write(data);
if not ok then
f:close();
os_remove(scratch);
return nil, msg;
end
ok, msg = f:close();
if not ok then
os_remove(scratch);
return nil, msg;
end
return os_rename(scratch, filename);
end
if prosody and prosody.platform ~= "posix" then
-- os.rename does not overwrite existing files on Windows
-- TODO We could use Transactional NTFS on Vista and above
function atomic_store(filename, data)
local f, err = io_open(filename, "w");
if not f then return f, err; end
local ok, msg = f:write(data);
if not ok then f:close(); return ok, msg; end
return f:close();
end
end
local function store(username, host, datastore, data)
if not data then
data = {};
end
username, host, datastore, data = callback(username, host, datastore, data);
if username == false then
return true; -- Don't save this data at all
end
-- save the datastore
local d = "return " .. serialize(data) .. ";\n";
local mkdir_cache_cleared;
repeat
local ok, msg = atomic_store(getpath(username, host, datastore, nil, true), d);
if not ok then
if not mkdir_cache_cleared then -- We may need to recreate a removed directory
_mkdir = {};
mkdir_cache_cleared = true;
else
log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
return nil, "Error saving to storage";
end
end
if next(data) == nil then -- try to delete empty datastore
log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
os_remove(getpath(username, host, datastore));
end
-- we write data even when we are deleting because lua doesn't have a
-- platform independent way of checking for non-exisitng files
until ok;
return true;
end
-- Append a blob of data to a file
local function append(username, host, datastore, ext, data)
if type(data) ~= "string" then return; end
local filename = getpath(username, host, datastore, ext, true);
local f = io_open(filename, "r+");
if not f then
return atomic_store(filename, data);
-- File did probably not exist, let's create it
end
local pos = f:seek("end");
local ok, msg = atomic_append(f, data);
if not ok then
f:close();
return ok, msg, "write";
end
ok, msg = f:close();
if not ok then
return ok, msg, "close";
end
return true, pos;
end
local function list_append(username, host, datastore, data)
if not data then return; end
if callback(username, host, datastore) == false then return true; end
-- save the datastore
data = "item(" .. serialize(data) .. ");\n";
local ok, msg, where = append(username, host, datastore, "list", data);
if not ok then
log("error", "Unable to write to %s storage ('%s' in %s) for user: %s@%s",
datastore, msg, where, username or "nil", host or "nil");
return ok, msg;
end
return true;
end
local function list_store(username, host, datastore, data)
if not data then
data = {};
end
if callback(username, host, datastore) == false then return true; end
-- save the datastore
local d = {};
for i, item in ipairs(data) do
d[i] = "item(" .. serialize(item) .. ");\n";
end
local ok, msg = atomic_store(getpath(username, host, datastore, "list", true), t_concat(d));
if not ok then
log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil");
return;
end
if next(data) == nil then -- try to delete empty datastore
log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
os_remove(getpath(username, host, datastore, "list"));
end
-- we write data even when we are deleting because lua doesn't have a
-- platform independent way of checking for non-exisitng files
return true;
end
local function list_load(username, host, datastore)
local items = {};
local data, err, errno = envloadfile(getpath(username, host, datastore, "list"), {item = function(i) t_insert(items, i); end});
if not data then
if errno == ENOENT then
-- No such file, ok to ignore
return nil;
end
log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, err, username or "nil", host or "nil");
return nil, "Error reading storage";
end
local success, ret = pcall(data);
if not success then
log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil");
return nil, "Error reading storage";
end
return items;
end
local type_map = {
keyval = "dat";
list = "list";
}
local function users(host, store, typ) -- luacheck: ignore 431/store
typ = type_map[typ or "keyval"];
local store_dir = format("%s/%s/%s", data_path, encode(host), store);
local mode, err = lfs.attributes(store_dir, "mode");
if not mode then
return function() log("debug", "%s", err or (store_dir .. " does not exist")) end
end
local next, state = lfs.dir(store_dir); -- luacheck: ignore 431/next 431/state
return function(state) -- luacheck: ignore 431/state
for node in next, state do
local file, ext = node:match("^(.*)%.([dalist]+)$");
if file and ext == typ then
return decode(file);
end
end
end, state;
end
local function stores(username, host, typ)
typ = type_map[typ or "keyval"];
local store_dir = format("%s/%s/", data_path, encode(host));
local mode, err = lfs.attributes(store_dir, "mode");
if not mode then
return function() log("debug", err or (store_dir .. " does not exist")) end
end
local next, state = lfs.dir(store_dir); -- luacheck: ignore 431/next 431/state
return function(state) -- luacheck: ignore 431/state
for node in next, state do
if not node:match"^%." then
if username == true then
if lfs.attributes(store_dir..node, "mode") == "directory" then
return decode(node);
end
elseif username then
local store_name = decode(node);
if lfs.attributes(getpath(username, host, store_name, typ), "mode") then
return store_name;
end
elseif lfs.attributes(node, "mode") == "file" then
local file, ext = node:match("^(.*)%.([dalist]+)$");
if ext == typ then
return decode(file)
end
end
end
end
end, state;
end
local function do_remove(path)
local ok, err = os_remove(path);
if not ok and lfs.attributes(path, "mode") then
return ok, err;
end
return true
end
local function purge(username, host)
local host_dir = format("%s/%s/", data_path, encode(host));
local ok, iter, state, var = pcall(lfs.dir, host_dir);
if not ok then
return ok, iter;
end
local errs = {};
for file in iter, state, var do
if lfs.attributes(host_dir..file, "mode") == "directory" then
local store_name = decode(file);
local ok, err = do_remove(getpath(username, host, store_name));
if not ok then errs[#errs+1] = err; end
local ok, err = do_remove(getpath(username, host, store_name, "list"));
if not ok then errs[#errs+1] = err; end
end
end
return #errs == 0, t_concat(errs, ", ");
end
return {
set_data_path = set_data_path;
add_callback = add_callback;
remove_callback = remove_callback;
getpath = getpath;
load = load;
store = store;
append_raw = append;
store_raw = atomic_store;
list_append = list_append;
list_store = list_store;
list_load = list_load;
users = users;
stores = stores;
purge = purge;
path_decode = decode;
path_encode = encode;
};
prosody-0.10.0/util/sasl_cyrus.lua 0000644 0001750 0001750 00000014376 13163172043 017055 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2009 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local cyrussasl = require "cyrussasl";
local log = require "util.logger".init("sasl_cyrus");
local setmetatable = setmetatable
local pcall = pcall
local s_match, s_gmatch = string.match, string.gmatch
local sasl_errstring = {
-- SASL result codes --
[1] = "another step is needed in authentication";
[0] = "successful result";
[-1] = "generic failure";
[-2] = "memory shortage failure";
[-3] = "overflowed buffer";
[-4] = "mechanism not supported";
[-5] = "bad protocol / cancel";
[-6] = "can't request info until later in exchange";
[-7] = "invalid parameter supplied";
[-8] = "transient failure (e.g., weak key)";
[-9] = "integrity check failed";
[-12] = "SASL library not initialized";
-- client only codes --
[2] = "needs user interaction";
[-10] = "server failed mutual authentication step";
[-11] = "mechanism doesn't support requested feature";
-- server only codes --
[-13] = "authentication failure";
[-14] = "authorization failure";
[-15] = "mechanism too weak for this user";
[-16] = "encryption needed to use mechanism";
[-17] = "One time use of a plaintext password will enable requested mechanism for user";
[-18] = "passphrase expired, has to be reset";
[-19] = "account disabled";
[-20] = "user not found";
[-23] = "version mismatch with plug-in";
[-24] = "remote authentication server unavailable";
[-26] = "user exists, but no verifier for user";
-- codes for password setting --
[-21] = "passphrase locked";
[-22] = "requested change was not needed";
[-27] = "passphrase is too weak for security policy";
[-28] = "user supplied passwords not permitted";
};
setmetatable(sasl_errstring, { __index = function() return "undefined error!" end });
local _ENV = nil;
local method = {};
method.__index = method;
local initialized = false;
local function init(service_name)
if not initialized then
local st, errmsg = pcall(cyrussasl.server_init, service_name);
if st then
initialized = true;
else
log("error", "Failed to initialize Cyrus SASL: %s", errmsg);
end
end
end
-- create a new SASL object which can be used to authenticate clients
-- host_fqdn may be nil in which case gethostname() gives the value.
-- For GSSAPI, this determines the hostname in the service ticket (after
-- reverse DNS canonicalization, only if [libdefaults] rdns = true which
-- is the default).
local function new(realm, service_name, app_name, host_fqdn)
init(app_name or service_name);
local st, ret = pcall(cyrussasl.server_new, service_name, host_fqdn, realm, nil, nil)
if not st then
log("error", "Creating SASL server connection failed: %s", ret);
return nil;
end
local sasl_i = { realm = realm, service_name = service_name, cyrus = ret };
if cyrussasl.set_canon_cb then
local c14n_cb = function (user)
local node = s_match(user, "^([^@]+)");
log("debug", "Canonicalizing username %s to %s", user, node)
return node
end
cyrussasl.set_canon_cb(sasl_i.cyrus, c14n_cb);
end
cyrussasl.setssf(sasl_i.cyrus, 0, 0xffffffff)
local mechanisms = {};
local cyrus_mechs = cyrussasl.listmech(sasl_i.cyrus, nil, "", " ", "");
for w in s_gmatch(cyrus_mechs, "[^ ]+") do
mechanisms[w] = true;
end
sasl_i.mechs = mechanisms;
return setmetatable(sasl_i, method);
end
-- get a fresh clone with the same realm and service name
function method:clean_clone()
return new(self.realm, self.service_name)
end
-- get a list of possible SASL mechanims to use
function method:mechanisms()
return self.mechs;
end
-- select a mechanism to use
function method:select(mechanism)
if not self.selected and self.mechs[mechanism] then
self.selected = mechanism;
return true;
end
end
-- feed new messages to process into the library
function method:process(message)
local err;
local data;
if not self.first_step_done then
err, data = cyrussasl.server_start(self.cyrus, self.selected, message or "")
self.first_step_done = true;
else
err, data = cyrussasl.server_step(self.cyrus, message or "")
end
self.username = cyrussasl.get_username(self.cyrus)
if (err == 0) then -- SASL_OK
if self.require_provisioning and not self.require_provisioning(self.username) then
return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP";
end
return "success", data
elseif (err == 1) then -- SASL_CONTINUE
return "challenge", data
elseif (err == -4) then -- SASL_NOMECH
log("debug", "SASL mechanism not available from remote end")
return "failure", "invalid-mechanism", "SASL mechanism not available"
elseif (err == -13) then -- SASL_BADAUTH
return "failure", "not-authorized", sasl_errstring[err];
else
log("debug", "Got SASL error condition %d: %s", err, sasl_errstring[err]);
return "failure", "undefined-condition", sasl_errstring[err];
end
end
return {
new = new;
};
prosody-0.10.0/util/debug.lua 0000644 0001750 0001750 00000014247 13163172043 015751 0 ustar matthew matthew -- Variables ending with these names will not
-- have their values printed ('password' includes
-- 'new_password', etc.)
--
-- luacheck: ignore 122/debug
local censored_names = {
password = true;
passwd = true;
pass = true;
pwd = true;
};
local optimal_line_length = 65;
local termcolours = require "util.termcolours";
local getstring = termcolours.getstring;
local styles;
do
local _ = termcolours.getstyle;
styles = {
boundary_padding = _("bright");
filename = _("bright", "blue");
level_num = _("green");
funcname = _("yellow");
location = _("yellow");
};
end
local function get_locals_table(thread, level)
local locals = {};
for local_num = 1, math.huge do
local name, value;
if thread then
name, value = debug.getlocal(thread, level, local_num);
else
name, value = debug.getlocal(level+1, local_num);
end
if not name then break; end
table.insert(locals, { name = name, value = value });
end
return locals;
end
local function get_upvalues_table(func)
local upvalues = {};
if func then
for upvalue_num = 1, math.huge do
local name, value = debug.getupvalue(func, upvalue_num);
if not name then break; end
table.insert(upvalues, { name = name, value = value });
end
end
return upvalues;
end
local function string_from_var_table(var_table, max_line_len, indent_str)
local var_string = {};
local col_pos = 0;
max_line_len = max_line_len or math.huge;
indent_str = "\n"..(indent_str or "");
for _, var in ipairs(var_table) do
local name, value = var.name, var.value;
if name:sub(1,1) ~= "(" then
if type(value) == "string" then
if censored_names[name:match("%a+$")] then
value = "";
else
value = ("%q"):format(value);
end
else
value = tostring(value);
end
if #value > max_line_len then
value = value:sub(1, max_line_len-3).."…";
end
local str = ("%s = %s"):format(name, tostring(value));
col_pos = col_pos + #str;
if col_pos > max_line_len then
table.insert(var_string, indent_str);
col_pos = 0;
end
table.insert(var_string, str);
end
end
if #var_string == 0 then
return nil;
else
return "{ "..table.concat(var_string, ", "):gsub(indent_str..", ", indent_str).." }";
end
end
local function get_traceback_table(thread, start_level)
local levels = {};
for level = start_level, math.huge do
local info;
if thread then
info = debug.getinfo(thread, level);
else
info = debug.getinfo(level+1);
end
if not info then break; end
levels[(level-start_level)+1] = {
level = level;
info = info;
locals = get_locals_table(thread, level+(thread and 0 or 1));
upvalues = get_upvalues_table(info.func);
};
end
return levels;
end
local function build_source_boundary_marker(last_source_desc)
local padding = string.rep("-", math.floor(((optimal_line_length - 6) - #last_source_desc)/2));
return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v "));
end
local function _traceback(thread, message, level)
-- Lua manual says: debug.traceback ([thread,] [message [, level]])
-- I fathom this to mean one of:
-- ()
-- (thread)
-- (message, level)
-- (thread, message, level)
if thread == nil then -- Defaults
thread, message, level = coroutine.running(), message, level;
elseif type(thread) == "string" then
thread, message, level = coroutine.running(), thread, message;
elseif type(thread) ~= "thread" then
return nil; -- debug.traceback() does this
end
level = level or 0;
message = message and (message.."\n") or "";
-- +3 counts for this function, and the pcall() and wrapper above us, the +1... I don't know.
local levels = get_traceback_table(thread, level+(thread == nil and 4 or 0));
local last_source_desc;
local lines = {};
for nlevel, level in ipairs(levels) do
local info = level.info;
local line = "...";
local func_type = info.namewhat.." ";
local source_desc = (info.short_src == "[C]" and "C code") or info.short_src or "Unknown";
if func_type == " " then func_type = ""; end;
if info.short_src == "[C]" then
line = "[ C ] "..func_type.."C function "..getstring(styles.location, (info.name and ("%q"):format(info.name) or "(unknown name)"));
elseif info.what == "main" then
line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline);
else
local name = info.name or " ";
if name ~= " " then
name = ("%q"):format(name);
end
if func_type == "global " or func_type == "local " then
func_type = func_type.."function ";
end
line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline).." in "..func_type..getstring(styles.funcname, name).." (defined on line "..info.linedefined..")";
end
if source_desc ~= last_source_desc then -- Venturing into a new source, add marker for previous
last_source_desc = source_desc;
table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
end
nlevel = nlevel-1;
table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line);
local npadding = (" "):rep(#tostring(nlevel));
if level.locals then
local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t "..npadding);
if locals_str then
table.insert(lines, "\t "..npadding.."Locals: "..locals_str);
end
end
local upvalues_str = string_from_var_table(level.upvalues, optimal_line_length, "\t "..npadding);
if upvalues_str then
table.insert(lines, "\t "..npadding.."Upvals: "..upvalues_str);
end
end
-- table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
return message.."stack traceback:\n"..table.concat(lines, "\n");
end
local function traceback(...)
local ok, ret = pcall(_traceback, ...);
if not ok then
return "Error in error handling: "..ret;
end
return ret;
end
local function use()
debug.traceback = traceback;
end
return {
get_locals_table = get_locals_table;
get_upvalues_table = get_upvalues_table;
string_from_var_table = string_from_var_table;
get_traceback_table = get_traceback_table;
traceback = traceback;
use = use;
};
prosody-0.10.0/util/xml.lua 0000644 0001750 0001750 00000002472 13163172043 015460 0 ustar matthew matthew
local st = require "util.stanza";
local lxp = require "lxp";
local _ENV = nil;
local parse_xml = (function()
local ns_prefixes = {
["http://www.w3.org/XML/1998/namespace"] = "xml";
};
local ns_separator = "\1";
local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
return function(xml)
--luacheck: ignore 212/self
local handler = {};
local stanza = st.stanza("root");
function handler:StartElement(tagname, attr)
local curr_ns,name = tagname:match(ns_pattern);
if name == "" then
curr_ns, name = "", curr_ns;
end
if curr_ns ~= "" then
attr.xmlns = curr_ns;
end
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
local ns, nm = k:match(ns_pattern);
if nm ~= "" then
ns = ns_prefixes[ns];
if ns then
attr[ns..":"..nm] = attr[k];
attr[k] = nil;
end
end
end
stanza:tag(name, attr);
end
function handler:CharacterData(data)
stanza:text(data);
end
function handler:EndElement()
stanza:up();
end
local parser = lxp.new(handler, "\1");
local ok, err, line, col = parser:parse(xml);
if ok then ok, err, line, col = parser:parse(); end
--parser:close();
if ok then
return stanza.tags[1];
else
return ok, err.." (line "..line..", col "..col..")";
end
end;
end)();
return {
parse = parse_xml;
};
prosody-0.10.0/util/array.lua 0000644 0001750 0001750 00000007575 13163172043 016007 0 ustar matthew matthew -- 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 t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
local setmetatable = setmetatable;
local math_random = math.random;
local math_floor = math.floor;
local pairs, ipairs = pairs, ipairs;
local tostring = tostring;
local type = type;
local array = {};
local array_base = {};
local array_methods = {};
local array_mt = { __index = array_methods, __tostring = function (self) return "{"..self:concat(", ").."}"; end };
local function new_array(self, t, _s, _var)
if type(t) == "function" then -- Assume iterator
t = self.collect(t, _s, _var);
end
return setmetatable(t or {}, array_mt);
end
function array_mt.__add(a1, a2)
local res = new_array();
return res:append(a1):append(a2);
end
function array_mt.__eq(a, b)
if #a == #b then
for i = 1, #a do
if a[i] ~= b[i] then
return false;
end
end
else
return false;
end
return true;
end
setmetatable(array, { __call = new_array });
-- Read-only methods
function array_methods:random()
return self[math_random(1, #self)];
end
-- These methods can be called two ways:
-- array.method(existing_array, [params [, ...]]) -- Create new array for result
-- existing_array:method([params, ...]) -- Transform existing array into result
--
function array_base.map(outa, ina, func)
for k, v in ipairs(ina) do
outa[k] = func(v);
end
return outa;
end
function array_base.filter(outa, ina, func)
local inplace, start_length = ina == outa, #ina;
local write = 1;
for read = 1, start_length do
local v = ina[read];
if func(v) then
outa[write] = v;
write = write + 1;
end
end
if inplace and write <= start_length then
for i = write, start_length do
outa[i] = nil;
end
end
return outa;
end
function array_base.sort(outa, ina, ...)
if ina ~= outa then
outa:append(ina);
end
t_sort(outa, ...);
return outa;
end
function array_base.unique(outa, ina)
local seen = {};
return array_base.filter(outa, ina, function (item)
if seen[item] then
return false;
else
seen[item] = true;
return true;
end
end);
end
function array_base.pluck(outa, ina, key)
for i = 1, #ina do
outa[i] = ina[i][key];
end
return outa;
end
function array_base.reverse(outa, ina)
local len = #ina;
if ina == outa then
local middle = math_floor(len/2);
len = len + 1;
local o; -- opposite
for i = 1, middle do
o = len - i;
outa[i], outa[o] = outa[o], outa[i];
end
else
local off = len + 1;
for i = 1, len do
outa[i] = ina[off - i];
end
end
return outa;
end
--- These methods only mutate the array
function array_methods:shuffle()
local len = #self;
for i = 1, #self do
local r = math_random(i, len);
self[i], self[r] = self[r], self[i];
end
return self;
end
function array_methods:append(ina)
local len, len2 = #self, #ina;
for i = 1, len2 do
self[len+i] = ina[i];
end
return self;
end
function array_methods:push(x)
t_insert(self, x);
return self;
end
array_methods.pop = t_remove;
function array_methods:concat(sep)
return t_concat(array.map(self, tostring), sep);
end
function array_methods:length()
return #self;
end
--- These methods always create a new array
function array.collect(f, s, var)
local t = {};
while true do
var = f(s, var);
if var == nil then break; end
t_insert(t, var);
end
return setmetatable(t, array_mt);
end
---
-- Setup methods from array_base
for method, f in pairs(array_base) do
local base_method = f;
-- Setup global array method which makes new array
array[method] = function (old_a, ...)
local a = new_array();
return base_method(a, old_a, ...);
end
-- Setup per-array (mutating) method
array_methods[method] = function (self, ...)
return base_method(self, self, ...);
end
end
return array;
prosody-0.10.0/util/caps.lua 0000644 0001750 0001750 00000004073 13163172043 015605 0 ustar matthew matthew -- 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 base64 = require "util.encodings".base64.encode;
local sha1 = require "util.hashes".sha1;
local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat;
local ipairs = ipairs;
local _ENV = nil;
local function calculate_hash(disco_info)
local identities, features, extensions = {}, {}, {};
for _, tag in ipairs(disco_info) do
if tag.name == "identity" then
t_insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or ""));
elseif tag.name == "feature" then
t_insert(features, tag.attr.var or "");
elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then
local form = {};
local FORM_TYPE;
for _, field in ipairs(tag.tags) do
if field.name == "field" and field.attr.var then
local values = {};
for _, val in ipairs(field.tags) do
val = #val.tags == 0 and val:get_text();
if val then t_insert(values, val); end
end
t_sort(values);
if field.attr.var == "FORM_TYPE" then
FORM_TYPE = values[1];
elseif #values > 0 then
t_insert(form, field.attr.var.."\0"..t_concat(values, "<"));
else
t_insert(form, field.attr.var);
end
end
end
t_sort(form);
form = t_concat(form, "<");
if FORM_TYPE then form = FORM_TYPE.."\0"..form; end
t_insert(extensions, form);
end
end
t_sort(identities);
t_sort(features);
t_sort(extensions);
if #identities > 0 then identities = t_concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end
if #features > 0 then features = t_concat(features, "<").."<"; else features = ""; end
if #extensions > 0 then extensions = t_concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end
local S = identities..features..extensions;
local ver = base64(sha1(S));
return ver, S;
end
return {
calculate_hash = calculate_hash;
};
prosody-0.10.0/util/stanza.lua 0000644 0001750 0001750 00000026204 13163172043 016157 0 ustar matthew matthew -- 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 t_insert = table.insert;
local t_remove = table.remove;
local t_concat = table.concat;
local s_format = string.format;
local s_match = string.match;
local tostring = tostring;
local setmetatable = setmetatable;
local getmetatable = getmetatable;
local pairs = pairs;
local ipairs = ipairs;
local type = type;
local s_gsub = string.gsub;
local s_sub = string.sub;
local s_find = string.find;
local os = os;
local do_pretty_printing = not os.getenv("WINDIR");
local getstyle, getstring;
if do_pretty_printing then
local ok, termcolours = pcall(require, "util.termcolours");
if ok then
getstyle, getstring = termcolours.getstyle, termcolours.getstring;
else
do_pretty_printing = nil;
end
end
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
local _ENV = nil;
local stanza_mt = { __type = "stanza" };
stanza_mt.__index = stanza_mt;
local function new_stanza(name, attr)
local stanza = { name = name, attr = attr or {}, tags = {} };
return setmetatable(stanza, stanza_mt);
end
local function is_stanza(s)
return getmetatable(s) == stanza_mt;
end
function stanza_mt:query(xmlns)
return self:tag("query", { xmlns = xmlns });
end
function stanza_mt:body(text, attr)
return self:tag("body", attr):text(text);
end
function stanza_mt:tag(name, attrs)
local s = new_stanza(name, attrs);
local last_add = self.last_add;
if not last_add then last_add = {}; self.last_add = last_add; end
(last_add[#last_add] or self):add_direct_child(s);
t_insert(last_add, s);
return self;
end
function stanza_mt:text(text)
local last_add = self.last_add;
(last_add and last_add[#last_add] or self):add_direct_child(text);
return self;
end
function stanza_mt:up()
local last_add = self.last_add;
if last_add then t_remove(last_add); end
return self;
end
function stanza_mt:reset()
self.last_add = nil;
return self;
end
function stanza_mt:add_direct_child(child)
if type(child) == "table" then
t_insert(self.tags, child);
end
t_insert(self, child);
end
function stanza_mt:add_child(child)
local last_add = self.last_add;
(last_add and last_add[#last_add] or self):add_direct_child(child);
return self;
end
function stanza_mt:get_child(name, xmlns)
for _, child in ipairs(self.tags) do
if (not name or child.name == name)
and ((not xmlns and self.attr.xmlns == child.attr.xmlns)
or child.attr.xmlns == xmlns) then
return child;
end
end
end
function stanza_mt:get_child_text(name, xmlns)
local tag = self:get_child(name, xmlns);
if tag then
return tag:get_text();
end
return nil;
end
function stanza_mt:child_with_name(name)
for _, child in ipairs(self.tags) do
if child.name == name then return child; end
end
end
function stanza_mt:child_with_ns(ns)
for _, child in ipairs(self.tags) do
if child.attr.xmlns == ns then return child; end
end
end
function stanza_mt:children()
local i = 0;
return function (a)
i = i + 1
return a[i];
end, self, i;
end
function stanza_mt:childtags(name, xmlns)
local tags = self.tags;
local start_i, max_i = 1, #tags;
return function ()
for i = start_i, max_i do
local v = tags[i];
if (not name or v.name == name)
and ((not xmlns and self.attr.xmlns == v.attr.xmlns)
or v.attr.xmlns == xmlns) then
start_i = i+1;
return v;
end
end
end;
end
function stanza_mt:maptags(callback)
local tags, curr_tag = self.tags, 1;
local n_children, n_tags = #self, #tags;
local i = 1;
while curr_tag <= n_tags and n_tags > 0 do
if self[i] == tags[curr_tag] then
local ret = callback(self[i]);
if ret == nil then
t_remove(self, i);
t_remove(tags, curr_tag);
n_children = n_children - 1;
n_tags = n_tags - 1;
i = i - 1;
curr_tag = curr_tag - 1;
else
self[i] = ret;
tags[curr_tag] = ret;
end
curr_tag = curr_tag + 1;
end
i = i + 1;
end
return self;
end
function stanza_mt:find(path)
local pos = 1;
local len = #path + 1;
repeat
local xmlns, name, text;
local char = s_sub(path, pos, pos);
if char == "@" then
return self.attr[s_sub(path, pos + 1)];
elseif char == "{" then
xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1);
end
name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos);
name = name ~= "" and name or nil;
if pos == len then
if text == "#" then
return self:get_child_text(name, xmlns);
end
return self:get_child(name, xmlns);
end
self = self:get_child(name, xmlns);
until not self
end
local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
local function _dostring(t, buf, self, _xml_escape, parentns)
local nsid = 0;
local name = t.name
t_insert(buf, "<"..name);
for k, v in pairs(t.attr) do
if s_find(k, "\1", 1, true) then
local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
nsid = nsid + 1;
t_insert(buf, " xmlns:ns"..nsid.."='".._xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='".._xml_escape(v).."'");
elseif not(k == "xmlns" and v == parentns) then
t_insert(buf, " "..k.."='".._xml_escape(v).."'");
end
end
local len = #t;
if len == 0 then
t_insert(buf, "/>");
else
t_insert(buf, ">");
for n=1,len do
local child = t[n];
if child.name then
self(child, buf, self, _xml_escape, t.attr.xmlns);
else
t_insert(buf, _xml_escape(child));
end
end
t_insert(buf, ""..name..">");
end
end
function stanza_mt.__tostring(t)
local buf = {};
_dostring(t, buf, _dostring, xml_escape, nil);
return t_concat(buf);
end
function stanza_mt.top_tag(t)
local attr_string = "";
if t.attr then
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end
end
return s_format("<%s%s>", t.name, attr_string);
end
function stanza_mt.get_text(t)
if #t.tags == 0 then
return t_concat(t);
end
end
function stanza_mt.get_error(stanza)
local error_type, condition, text;
local error_tag = stanza:get_child("error");
if not error_tag then
return nil, nil, nil;
end
error_type = error_tag.attr.type;
for _, child in ipairs(error_tag.tags) do
if child.attr.xmlns == xmlns_stanzas then
if not text and child.name == "text" then
text = child:get_text();
elseif not condition then
condition = child.name;
end
if condition and text then
break;
end
end
end
return error_type, condition or "undefined-condition", text;
end
local id = 0;
local function new_id()
id = id + 1;
return "lx"..id;
end
local function preserialize(stanza)
local s = { name = stanza.name, attr = stanza.attr };
for _, child in ipairs(stanza) do
if type(child) == "table" then
t_insert(s, preserialize(child));
else
t_insert(s, child);
end
end
return s;
end
local function deserialize(stanza)
-- Set metatable
if stanza then
local attr = stanza.attr;
for i=1,#attr do attr[i] = nil; end
local attrx = {};
for att in pairs(attr) do
if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then
local ns,na = s_match(att, "^([^|]+)|(.+)$");
attrx[ns.."\1"..na] = attr[att];
attr[att] = nil;
end
end
for a,v in pairs(attrx) do
attr[a] = v;
end
setmetatable(stanza, stanza_mt);
for _, child in ipairs(stanza) do
if type(child) == "table" then
deserialize(child);
end
end
if not stanza.tags then
-- Rebuild tags
local tags = {};
for _, child in ipairs(stanza) do
if type(child) == "table" then
t_insert(tags, child);
end
end
stanza.tags = tags;
end
end
return stanza;
end
local function clone(stanza)
local attr, tags = {}, {};
for k,v in pairs(stanza.attr) do attr[k] = v; end
local new = { name = stanza.name, attr = attr, tags = tags };
for i=1,#stanza do
local child = stanza[i];
if child.name then
child = clone(child);
t_insert(tags, child);
end
t_insert(new, child);
end
return setmetatable(new, stanza_mt);
end
local function message(attr, body)
if not body then
return new_stanza("message", attr);
else
return new_stanza("message", attr):tag("body"):text(body):up();
end
end
local function iq(attr)
if attr and not attr.id then attr.id = new_id(); end
return new_stanza("iq", attr or { id = new_id() });
end
local function reply(orig)
return new_stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
end
local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
local function error_reply(orig, error_type, condition, error_message)
local t = reply(orig);
t.attr.type = "error";
t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here
:tag(condition, xmpp_stanzas_attr):up();
if error_message then t:tag("text", xmpp_stanzas_attr):text(error_message):up(); end
return t; -- stanza ready for adding app-specific errors
end
local function presence(attr)
return new_stanza("presence", attr);
end
if do_pretty_printing then
local style_attrk = getstyle("yellow");
local style_attrv = getstyle("red");
local style_tagname = getstyle("red");
local style_punc = getstyle("magenta");
local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
--local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
local tag_format = top_tag_format.."%s"..getstring(style_punc, "")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
function stanza_mt.pretty_print(t)
local children_text = "";
for _, child in ipairs(t) do
if type(child) == "string" then
children_text = children_text .. xml_escape(child);
else
children_text = children_text .. child:pretty_print();
end
end
local attr_string = "";
if t.attr then
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
end
return s_format(tag_format, t.name, attr_string, children_text, t.name);
end
function stanza_mt.pretty_top_tag(t)
local attr_string = "";
if t.attr then
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
end
return s_format(top_tag_format, t.name, attr_string);
end
else
-- Sorry, fresh out of colours for you guys ;)
stanza_mt.pretty_print = stanza_mt.__tostring;
stanza_mt.pretty_top_tag = stanza_mt.top_tag;
end
return {
stanza_mt = stanza_mt;
stanza = new_stanza;
is_stanza = is_stanza;
new_id = new_id;
preserialize = preserialize;
deserialize = deserialize;
clone = clone;
message = message;
iq = iq;
reply = reply;
error_reply = error_reply;
presence = presence;
xml_escape = xml_escape;
};
prosody-0.10.0/util/statsd.lua 0000644 0001750 0001750 00000003752 13163172043 016164 0 ustar matthew matthew local socket = require "socket";
local time = require "util.time".now
local function new(config)
if not config or not config.statsd_server then
return nil, "No statsd server specified in the config, please see https://prosody.im/doc/statistics";
end
local sock = socket.udp();
sock:setpeername(config.statsd_server, config.statsd_port or 8125);
local prefix = (config.prefix or "prosody")..".";
local function send_metric(s)
return sock:send(prefix..s);
end
local function send_gauge(name, amount, relative)
local s_amount = tostring(amount);
if relative and amount > 0 then
s_amount = "+"..s_amount;
end
return send_metric(name..":"..s_amount.."|g");
end
local function send_counter(name, amount)
return send_metric(name..":"..tostring(amount).."|c");
end
local function send_duration(name, duration)
return send_metric(name..":"..tostring(duration).."|ms");
end
local function send_histogram_sample(name, sample)
return send_metric(name..":"..tostring(sample).."|h");
end
local methods;
methods = {
amount = function (name, initial)
if initial then
send_gauge(name, initial);
end
return function (new_v) send_gauge(name, new_v); end
end;
counter = function (name, initial) --luacheck: ignore 212/initial
return function (delta)
send_gauge(name, delta, true);
end;
end;
rate = function (name)
return function ()
send_counter(name, 1);
end;
end;
distribution = function (name, unit, type) --luacheck: ignore 212/unit 212/type
return function (value)
send_histogram_sample(name, value);
end;
end;
sizes = function (name)
name = name.."_size";
return function (value)
send_histogram_sample(name, value);
end;
end;
times = function (name)
return function ()
local start_time = time();
return function ()
local end_time = time();
local duration = end_time - start_time;
send_duration(name, duration*1000);
end
end;
end;
};
return methods;
end
return {
new = new;
}
prosody-0.10.0/util/multitable.lua 0000644 0001750 0001750 00000007132 13163172043 017020 0 ustar matthew matthew -- 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 select = select;
local t_insert = table.insert;
local pairs, next, type = pairs, next, type;
local unpack = table.unpack or unpack; --luacheck: ignore 113
local _ENV = nil;
local function get(self, ...)
local t = self.data;
for n = 1,select('#', ...) do
t = t[select(n, ...)];
if not t then break; end
end
return t;
end
local function add(self, ...)
local t = self.data;
local count = select('#', ...);
for n = 1,count-1 do
local key = select(n, ...);
local tab = t[key];
if not tab then tab = {}; t[key] = tab; end
t = tab;
end
t_insert(t, (select(count, ...)));
end
local function set(self, ...)
local t = self.data;
local count = select('#', ...);
for n = 1,count-2 do
local key = select(n, ...);
local tab = t[key];
if not tab then tab = {}; t[key] = tab; end
t = tab;
end
t[(select(count-1, ...))] = (select(count, ...));
end
local function r(t, n, _end, ...)
if t == nil then return; end
local k = select(n, ...);
if n == _end then
t[k] = nil;
return;
end
if k then
local v = t[k];
if v then
r(v, n+1, _end, ...);
if not next(v) then
t[k] = nil;
end
end
else
for _,b in pairs(t) do
r(b, n+1, _end, ...);
if not next(b) then
t[_] = nil;
end
end
end
end
local function remove(self, ...)
local _end = select('#', ...);
for n = _end,1 do
if select(n, ...) then _end = n; break; end
end
r(self.data, 1, _end, ...);
end
local function s(t, n, results, _end, ...)
if t == nil then return; end
local k = select(n, ...);
if n == _end then
if k == nil then
for _, v in pairs(t) do
t_insert(results, v);
end
else
t_insert(results, t[k]);
end
return;
end
if k then
local v = t[k];
if v then
s(v, n+1, results, _end, ...);
end
else
for _,b in pairs(t) do
s(b, n+1, results, _end, ...);
end
end
end
-- Search for keys, nil == wildcard
local function search(self, ...)
local _end = select('#', ...);
for n = _end,1 do
if select(n, ...) then _end = n; break; end
end
local results = {};
s(self.data, 1, results, _end, ...);
return results;
end
-- Append results to an existing list
local function search_add(self, results, ...)
if not results then results = {}; end
local _end = select('#', ...);
for n = _end,1 do
if select(n, ...) then _end = n; break; end
end
s(self.data, 1, results, _end, ...);
return results;
end
local function iter(self, ...)
local query = { ... };
local maxdepth = select("#", ...);
local stack = { self.data };
local keys = { };
local function it(self)
local depth = #stack;
local key = next(stack[depth], keys[depth]);
if key == nil then -- Go up the stack
stack[depth], keys[depth] = nil, nil;
if depth > 1 then
return it(self);
end
return; -- The end
else
keys[depth] = key;
end
local value = stack[depth][key];
if query[depth] == nil or key == query[depth] then
if depth == maxdepth then -- Result
local result = {}; -- Collect keys forming path to result
for i = 1, depth do
result[i] = keys[i];
end
result[depth+1] = value;
return unpack(result, 1, depth+1);
elseif type(value) == "table" then
t_insert(stack, value); -- Descend
end
end
return it(self);
end;
return it, self;
end
local function new()
return {
data = {};
get = get;
add = add;
set = set;
remove = remove;
search = search;
search_add = search_add;
iter = iter;
};
end
return {
iter = iter;
new = new;
};
prosody-0.10.0/util/filters.lua 0000644 0001750 0001750 00000004057 13163172043 016331 0 ustar matthew matthew -- 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 t_insert, t_remove = table.insert, table.remove;
local _ENV = nil;
local new_filter_hooks = {};
local function initialize(session)
if not session.filters then
local filters = {};
session.filters = filters;
function session.filter(type, data)
local filter_list = filters[type];
if filter_list then
for i = 1, #filter_list do
data = filter_list[i](data, session);
if data == nil then break; end
end
end
return data;
end
end
for i=1,#new_filter_hooks do
new_filter_hooks[i](session);
end
return session.filter;
end
local function add_filter(session, type, callback, priority)
if not session.filters then
initialize(session);
end
local filter_list = session.filters[type];
if not filter_list then
filter_list = {};
session.filters[type] = filter_list;
elseif filter_list[callback] then
return; -- Filter already added
end
priority = priority or 0;
local i = 0;
repeat
i = i + 1;
until not filter_list[i] or filter_list[filter_list[i]] < priority;
t_insert(filter_list, i, callback);
filter_list[callback] = priority;
end
local function remove_filter(session, type, callback)
if not session.filters then return; end
local filter_list = session.filters[type];
if filter_list and filter_list[callback] then
for i=1, #filter_list do
if filter_list[i] == callback then
t_remove(filter_list, i);
filter_list[callback] = nil;
return true;
end
end
end
end
local function add_filter_hook(callback)
t_insert(new_filter_hooks, callback);
end
local function remove_filter_hook(callback)
for i=1,#new_filter_hooks do
if new_filter_hooks[i] == callback then
t_remove(new_filter_hooks, i);
end
end
end
return {
initialize = initialize;
add_filter = add_filter;
remove_filter = remove_filter;
add_filter_hook = add_filter_hook;
remove_filter_hook = remove_filter_hook;
};
prosody-0.10.0/util/queue.lua 0000644 0001750 0001750 00000003274 13163172043 016005 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2015 Matthew Wild
-- Copyright (C) 2008-2015 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- Small ringbuffer library (i.e. an efficient FIFO queue with a size limit)
-- (because unbounded dynamically-growing queues are a bad thing...)
local have_utable, utable = pcall(require, "util.table"); -- For pre-allocation of table
local function new(size, allow_wrapping)
-- Head is next insert, tail is next read
local head, tail = 1, 1;
local items = 0; -- Number of stored items
local t = have_utable and utable.create(size, 0) or {}; -- Table to hold items
--luacheck: ignore 212/self
return {
_items = t;
size = size;
count = function (self) return items; end;
push = function (self, item)
if items >= size then
if allow_wrapping then
tail = (tail%size)+1; -- Advance to next oldest item
items = items - 1;
else
return nil, "queue full";
end
end
t[head] = item;
items = items + 1;
head = (head%size)+1;
return true;
end;
pop = function (self)
if items == 0 then
return nil;
end
local item;
item, t[tail] = t[tail], 0;
tail = (tail%size)+1;
items = items - 1;
return item;
end;
peek = function (self)
if items == 0 then
return nil;
end
return t[tail];
end;
items = function (self)
--luacheck: ignore 431/t
return function (t, pos)
if pos >= t:count() then
return nil;
end
local read_pos = tail + pos;
if read_pos > t.size then
read_pos = (read_pos%size);
end
return pos+1, t._items[read_pos];
end, self, 0;
end;
};
end
return {
new = new;
};
prosody-0.10.0/util/adhoc.lua 0000644 0001750 0001750 00000001732 13163172043 015734 0 ustar matthew matthew local function new_simple_form(form, result_handler)
return function(self, data, state)
if state then
if data.action == "cancel" then
return { status = "canceled" };
end
local fields, err = form:data(data.form);
return result_handler(fields, err, data);
else
return { status = "executing", actions = {"next", "complete", default = "complete"}, form = form }, "executing";
end
end
end
local function new_initial_data_form(form, initial_data, result_handler)
return function(self, data, state)
if state then
if data.action == "cancel" then
return { status = "canceled" };
end
local fields, err = form:data(data.form);
return result_handler(fields, err, data);
else
return { status = "executing", actions = {"next", "complete", default = "complete"},
form = { layout = form, values = initial_data(data) } }, "executing";
end
end
end
return { new_simple_form = new_simple_form,
new_initial_data_form = new_initial_data_form };
prosody-0.10.0/util/sasl/ 0000775 0001750 0001750 00000000000 13163172043 015114 5 ustar matthew matthew prosody-0.10.0/util/sasl/digest-md5.lua 0000644 0001750 0001750 00000022223 13163172043 017560 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local tostring = tostring;
local type = type;
local s_gmatch = string.gmatch;
local s_match = string.match;
local t_concat = table.concat;
local t_insert = table.insert;
local to_byte, to_char = string.byte, string.char;
local md5 = require "util.hashes".md5;
local log = require "util.logger".init("sasl");
local generate_uuid = require "util.uuid".generate;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local _ENV = nil;
--=========================
--SASL DIGEST-MD5 according to RFC 2831
--[[
Supported Authentication Backends
digest_md5:
function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
-- implementations it's not
return digesthash, state;
end
digest_md5_test:
function(username, domain, realm, encoding, digesthash)
return true or false, state;
end
]]
local function digest(self, message)
--TODO complete support for authzid
local function serialize(message)
local data = ""
-- testing all possible values
if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
if message["charset"] then data = data..[[charset=]]..message.charset.."," end
if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
data = data:gsub(",$", "")
return data
end
local function utf8tolatin1ifpossible(passwd)
local i = 1;
while i <= #passwd do
local passwd_i = to_byte(passwd:sub(i, i));
if passwd_i > 0x7F then
if passwd_i < 0xC0 or passwd_i > 0xC3 then
return passwd;
end
i = i + 1;
passwd_i = to_byte(passwd:sub(i, i));
if passwd_i < 0x80 or passwd_i > 0xBF then
return passwd;
end
end
i = i + 1;
end
local p = {};
local j = 0;
i = 1;
while (i <= #passwd) do
local passwd_i = to_byte(passwd:sub(i, i));
if passwd_i > 0x7F then
i = i + 1;
local passwd_i_1 = to_byte(passwd:sub(i, i));
t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
else
t_insert(p, to_char(passwd_i));
end
i = i + 1;
end
return t_concat(p);
end
local function latin1toutf8(str)
local p = {};
for ch in s_gmatch(str, ".") do
ch = to_byte(ch);
if (ch < 0x80) then
t_insert(p, to_char(ch));
elseif (ch < 0xC0) then
t_insert(p, to_char(0xC2, ch));
else
t_insert(p, to_char(0xC3, ch - 64));
end
end
return t_concat(p);
end
local function parse(data)
local message = {}
-- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
for k, v in s_gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
message[k] = v;
end
return message;
end
if not self.nonce then
self.nonce = generate_uuid();
self.step = 0;
self.nonce_count = {};
end
self.step = self.step + 1;
if (self.step == 1) then
local challenge = serialize({ nonce = self.nonce,
qop = "auth",
charset = "utf-8",
algorithm = "md5-sess",
realm = self.realm});
return "challenge", challenge;
elseif (self.step == 2) then
local response = parse(message);
-- check for replay attack
if response["nc"] then
if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
end
-- check for username, it's REQUIRED by RFC 2831
local username = response["username"];
local _nodeprep = self.profile.nodeprep;
if username and _nodeprep ~= false then
username = (_nodeprep or nodeprep)(username); -- FIXME charset
end
if not username or username == "" then
return "failure", "malformed-request";
end
self.username = username;
-- check for nonce, ...
if not response["nonce"] then
return "failure", "malformed-request";
else
-- check if it's the right nonce
if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
end
if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
if not response["qop"] then response["qop"] = "auth" end
if response["realm"] == nil or response["realm"] == "" then
response["realm"] = "";
elseif response["realm"] ~= self.realm then
return "failure", "not-authorized", "Incorrect realm value";
end
local decoder;
if response["charset"] == nil then
decoder = utf8tolatin1ifpossible;
elseif response["charset"] ~= "utf-8" then
return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
end
local domain = "";
local protocol = "";
if response["digest-uri"] then
protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
if protocol == nil or domain == nil then return "failure", "malformed-request" end
else
return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
end
--TODO maybe realm support
local Y, state;
if self.profile.plain then
local password, state = self.profile.plain(self, response["username"], self.realm)
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
Y = md5(response["username"]..":"..response["realm"]..":"..password);
elseif self.profile["digest-md5"] then
Y, state = self.profile["digest-md5"](self, response["username"], self.realm, response["realm"], response["charset"])
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
elseif self.profile["digest-md5-test"] then
-- TODO
end
--local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
--if Y == nil then return "failure", "not-authorized"
--elseif Y == false then return "failure", "account-disabled" end
local A1 = "";
if response.authzid then
if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
-- COMPAT
log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
else
return "failure", "invalid-authzid";
end
else
A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
end
local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
local HA1 = md5(A1, true);
local HA2 = md5(A2, true);
local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
local response_value = md5(KD, true);
if response_value == response["response"] then
-- calculate rspauth
A2 = ":"..protocol.."/"..domain;
HA1 = md5(A1, true);
HA2 = md5(A2, true);
KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
local rspauth = md5(KD, true);
self.authenticated = true;
--TODO: considering sending the rspauth in a success node for saving one roundtrip; allowed according to http://tools.ietf.org/html/draft-saintandre-rfc3920bis-09#section-7.3.6
return "challenge", serialize({rspauth = rspauth});
else
return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
end
elseif self.step == 3 then
if self.authenticated ~= nil then return "success"
else return "failure", "malformed-request" end
end
end
local function init(registerMechanism)
registerMechanism("DIGEST-MD5", {"plain"}, digest);
end
return {
init = init;
}
prosody-0.10.0/util/sasl/scram.lua 0000644 0001750 0001750 00000024361 13163172043 016730 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local s_match = string.match;
local type = type
local base64 = require "util.encodings".base64;
local hmac_sha1 = require "util.hashes".hmac_sha1;
local sha1 = require "util.hashes".sha1;
local Hi = require "util.hashes".scram_Hi_sha1;
local generate_uuid = require "util.uuid".generate;
local saslprep = require "util.encodings".stringprep.saslprep;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local log = require "util.logger".init("sasl");
local t_concat = table.concat;
local char = string.char;
local byte = string.byte;
local _ENV = nil;
--=========================
--SASL SCRAM-SHA-1 according to RFC 5802
--[[
Supported Authentication Backends
scram_{MECH}:
-- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_'
function(username, realm)
return stored_key, server_key, iteration_count, salt, state;
end
Supported Channel Binding Backends
'tls-unique' according to RFC 5929
]]
local default_i = 4096
local xor_map = {0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;1;0;3;2;5;4;7;6;9;8;11;10;13;12;15;14;2;3;0;1;6;7;4;5;10;11;8;9;14;15;12;13;3;2;1;0;7;6;5;4;11;10;9;8;15;14;13;12;4;5;6;7;0;1;2;3;12;13;14;15;8;9;10;11;5;4;7;6;1;0;3;2;13;12;15;14;9;8;11;10;6;7;4;5;2;3;0;1;14;15;12;13;10;11;8;9;7;6;5;4;3;2;1;0;15;14;13;12;11;10;9;8;8;9;10;11;12;13;14;15;0;1;2;3;4;5;6;7;9;8;11;10;13;12;15;14;1;0;3;2;5;4;7;6;10;11;8;9;14;15;12;13;2;3;0;1;6;7;4;5;11;10;9;8;15;14;13;12;3;2;1;0;7;6;5;4;12;13;14;15;8;9;10;11;4;5;6;7;0;1;2;3;13;12;15;14;9;8;11;10;5;4;7;6;1;0;3;2;14;15;12;13;10;11;8;9;6;7;4;5;2;3;0;1;15;14;13;12;11;10;9;8;7;6;5;4;3;2;1;0;};
local result = {};
local function binaryXOR( a, b )
for i=1, #a do
local x, y = byte(a, i), byte(b, i);
local lowx, lowy = x % 16, y % 16;
local hix, hiy = (x - lowx) / 16, (y - lowy) / 16;
local lowr, hir = xor_map[lowx * 16 + lowy + 1], xor_map[hix * 16 + hiy + 1];
local r = hir * 16 + lowr;
result[i] = char(r)
end
return t_concat(result);
end
local function validate_username(username, _nodeprep)
-- check for forbidden char sequences
for eq in username:gmatch("=(.?.?)") do
if eq ~= "2C" and eq ~= "3D" then
return false
end
end
-- replace =2C with , and =3D with =
username = username:gsub("=2C", ",");
username = username:gsub("=3D", "=");
-- apply SASLprep
username = saslprep(username);
if username and _nodeprep ~= false then
username = (_nodeprep or nodeprep)(username);
end
return username and #username>0 and username;
end
local function hashprep(hashname)
return hashname:lower():gsub("-", "_");
end
local function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then
return false, "inappropriate argument types"
end
if iteration_count < 4096 then
log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.")
end
local salted_password = Hi(password, salt, iteration_count);
local stored_key = sha1(hmac_sha1(salted_password, "Client Key"))
local server_key = hmac_sha1(salted_password, "Server Key");
return true, stored_key, server_key
end
local function scram_gen(hash_name, H_f, HMAC_f)
local profile_name = "scram_" .. hashprep(hash_name);
local function scram_hash(self, message)
local support_channel_binding = false;
if self.profile.cb then support_channel_binding = true; end
if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end
local state = self.state;
if not state then
-- we are processing client_first_message
local client_first_message = message;
-- TODO: fail if authzid is provided, since we don't support them yet
local gs2_header, gs2_cbind_flag, gs2_cbind_name, authzid, client_first_message_bare, username, clientnonce
= s_match(client_first_message, "^(([pny])=?([^,]*),([^,]*),)(m?=?[^,]*,?n=([^,]*),r=([^,]*),?.*)$");
if not gs2_cbind_flag then
return "failure", "malformed-request";
end
if support_channel_binding and gs2_cbind_flag == "y" then
-- "y" -> client does support channel binding
-- but thinks the server does not.
return "failure", "malformed-request";
end
if gs2_cbind_flag == "n" then
-- "n" -> client doesn't support channel binding.
support_channel_binding = false;
end
if support_channel_binding and gs2_cbind_flag == "p" then
-- check whether we support the proposed channel binding type
if not self.profile.cb[gs2_cbind_name] then
return "failure", "malformed-request", "Proposed channel binding type isn't supported.";
end
else
-- no channel binding,
gs2_cbind_name = nil;
end
username = validate_username(username, self.profile.nodeprep);
if not username then
log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
return "failure", "malformed-request", "Invalid username.";
end
self.username = username;
-- retreive credentials
local stored_key, server_key, salt, iteration_count;
if self.profile.plain then
local password, status = self.profile.plain(self, username, self.realm)
if status == nil then return "failure", "not-authorized"
elseif status == false then return "failure", "account-disabled" end
password = saslprep(password);
if not password then
log("debug", "Password violates SASLprep.");
return "failure", "not-authorized", "Invalid password."
end
salt = generate_uuid();
iteration_count = default_i;
local succ;
succ, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
if not succ then
log("error", "Generating authentication database failed. Reason: %s", stored_key);
return "failure", "temporary-auth-failure";
end
elseif self.profile[profile_name] then
local status;
stored_key, server_key, iteration_count, salt, status = self.profile[profile_name](self, username, self.realm);
if status == nil then return "failure", "not-authorized"
elseif status == false then return "failure", "account-disabled" end
end
local nonce = clientnonce .. generate_uuid();
local server_first_message = "r="..nonce..",s="..base64.encode(salt)..",i="..iteration_count;
self.state = {
gs2_header = gs2_header;
gs2_cbind_name = gs2_cbind_name;
username = username;
nonce = nonce;
server_key = server_key;
stored_key = stored_key;
client_first_message_bare = client_first_message_bare;
server_first_message = server_first_message;
}
return "challenge", server_first_message
else
-- we are processing client_final_message
local client_final_message = message;
local client_final_message_without_proof, channelbinding, nonce, proof
= s_match(client_final_message, "(c=([^,]*),r=([^,]*),?.-),p=(.*)$");
if not proof or not nonce or not channelbinding then
return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
end
local client_gs2_header = base64.decode(channelbinding)
local our_client_gs2_header = state["gs2_header"]
if state.gs2_cbind_name then
-- we support channelbinding, so check if the value is valid
our_client_gs2_header = our_client_gs2_header .. self.profile.cb[state.gs2_cbind_name](self);
end
if client_gs2_header ~= our_client_gs2_header then
return "failure", "malformed-request", "Invalid channel binding value.";
end
if nonce ~= state.nonce then
return "failure", "malformed-request", "Wrong nonce in client-final-message.";
end
local ServerKey = state.server_key;
local StoredKey = state.stored_key;
local AuthMessage = state.client_first_message_bare .. "," .. state.server_first_message .. "," .. client_final_message_without_proof
local ClientSignature = HMAC_f(StoredKey, AuthMessage)
local ClientKey = binaryXOR(ClientSignature, base64.decode(proof))
local ServerSignature = HMAC_f(ServerKey, AuthMessage)
if StoredKey == H_f(ClientKey) then
local server_final_message = "v="..base64.encode(ServerSignature);
return "success", server_final_message;
else
return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
end
end
end
return scram_hash;
end
local function init(registerMechanism)
local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash));
-- register channel binding equivalent
registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash), {"tls-unique"});
end
registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
end
return {
getAuthenticationDatabaseSHA1 = getAuthenticationDatabaseSHA1;
init = init;
}
prosody-0.10.0/util/sasl/external.lua 0000644 0001750 0001750 00000001103 13163172043 017432 0 ustar matthew matthew local saslprep = require "util.encodings".stringprep.saslprep;
local _ENV = nil;
local function external(self, message)
message = saslprep(message);
local state
self.username, state = self.profile.external(message);
if state == false then
return "failure", "account-disabled";
elseif state == nil then
return "failure", "not-authorized";
elseif state == "expired" then
return "false", "credentials-expired";
end
return "success";
end
local function init(registerMechanism)
registerMechanism("EXTERNAL", {"external"}, external);
end
return {
init = init;
}
prosody-0.10.0/util/sasl/anonymous.lua 0000644 0001750 0001750 00000004267 13163172043 017656 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local generate_uuid = require "util.uuid".generate;
local _ENV = nil;
--=========================
--SASL ANONYMOUS according to RFC 4505
--[[
Supported Authentication Backends
anonymous:
function(username, realm)
return true; --for normal usage just return true; if you don't like the supplied username you can return false.
end
]]
local function anonymous(self, message)
local username;
repeat
username = generate_uuid();
until self.profile.anonymous(self, username, self.realm);
self.username = username;
return "success"
end
local function init(registerMechanism)
registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
end
return {
init = init;
}
prosody-0.10.0/util/sasl/plain.lua 0000644 0001750 0001750 00000007175 13163172043 016732 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local s_match = string.match;
local saslprep = require "util.encodings".stringprep.saslprep;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local log = require "util.logger".init("sasl");
local _ENV = nil;
-- ================================
-- SASL PLAIN according to RFC 4616
--[[
Supported Authentication Backends
plain:
function(username, realm)
return password, state;
end
plain_test:
function(username, password, realm)
return true or false, state;
end
]]
local function plain(self, message)
if not message then
return "failure", "malformed-request";
end
local authorization, authentication, password = s_match(message, "^([^%z]*)%z([^%z]+)%z([^%z]+)");
if not authorization then
return "failure", "malformed-request";
end
-- SASLprep password and authentication
authentication = saslprep(authentication);
password = saslprep(password);
if (not password) or (password == "") or (not authentication) or (authentication == "") then
log("debug", "Username or password violates SASLprep.");
return "failure", "malformed-request", "Invalid username or password.";
end
local _nodeprep = self.profile.nodeprep;
if _nodeprep ~= false then
authentication = (_nodeprep or nodeprep)(authentication);
if not authentication or authentication == "" then
return "failure", "malformed-request", "Invalid username or password."
end
end
self.username = authentication
local correct, state = false, false;
if self.profile.plain then
local correct_password;
correct_password, state = self.profile.plain(self, authentication, self.realm);
correct = (correct_password == password);
elseif self.profile.plain_test then
correct, state = self.profile.plain_test(self, authentication, password, self.realm);
end
if state == false then
return "failure", "account-disabled";
elseif state == nil or not correct then
return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent.";
end
return "success";
end
local function init(registerMechanism)
registerMechanism("PLAIN", {"plain", "plain_test"}, plain);
end
return {
init = init;
}
prosody-0.10.0/util/termcolours.lua 0000644 0001750 0001750 00000010414 13163172043 017231 0 ustar matthew matthew -- 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.
--
--
-- luacheck: ignore 213/i
local t_concat, t_insert = table.concat, table.insert;
local char, format = string.char, string.format;
local tonumber = tonumber;
local ipairs = ipairs;
local io_write = io.write;
local m_floor = math.floor;
local type = type;
local setmetatable = setmetatable;
local pairs = pairs;
local windows;
if os.getenv("WINDIR") then
windows = require "util.windows";
end
local orig_color = windows and windows.get_consolecolor and windows.get_consolecolor();
local _ENV = nil;
local stylemap = {
reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8;
black = 30; red = 31; green = 32; yellow = 33; blue = 34; magenta = 35; cyan = 36; white = 37;
["black background"] = 40; ["red background"] = 41; ["green background"] = 42; ["yellow background"] = 43; ["blue background"] = 44; ["magenta background"] = 45; ["cyan background"] = 46; ["white background"] = 47;
bold = 1, dark = 2, underline = 4, underlined = 4, normal = 0;
}
local winstylemap = {
["0"] = orig_color, -- reset
["1"] = 7+8, -- bold
["1;33"] = 2+4+8, -- bold yellow
["1;31"] = 4+8 -- bold red
}
local cssmap = {
[1] = "font-weight: bold", [2] = "opacity: 0.5", [4] = "text-decoration: underline", [8] = "visibility: hidden",
[30] = "color:black", [31] = "color:red", [32]="color:green", [33]="color:#FFD700",
[34] = "color:blue", [35] = "color: magenta", [36] = "color:cyan", [37] = "color: white",
[40] = "background-color:black", [41] = "background-color:red", [42]="background-color:green",
[43]="background-color:yellow", [44] = "background-color:blue", [45] = "background-color: magenta",
[46] = "background-color:cyan", [47] = "background-color: white";
};
local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m";
local function getstring(style, text)
if style then
return format(fmt_string, style, text);
else
return text;
end
end
local function gray(n)
return m_floor(n*3/32)+0xe8;
end
local function color(r,g,b)
if r == g and g == b then
return gray(r);
end
r = m_floor(r*3/128);
g = m_floor(g*3/128);
b = m_floor(b*3/128);
return 0x10 + ( r * 36 ) + ( g * 6 ) + ( b );
end
local function hex2rgb(hex)
local r = tonumber(hex:sub(1,2),16);
local g = tonumber(hex:sub(3,4),16);
local b = tonumber(hex:sub(5,6),16);
return r,g,b;
end
setmetatable(stylemap, { __index = function(_, style)
if type(style) == "string" and style:find("%x%x%x%x%x%x") == 1 then
local g = style:sub(7) == " background" and "48;5;" or "38;5;";
return g .. color(hex2rgb(style));
end
end } );
local csscolors = {
red = "ff0000"; fuchsia = "ff00ff"; green = "008000"; white = "ffffff";
lime = "00ff00"; yellow = "ffff00"; purple = "800080"; blue = "0000ff";
aqua = "00ffff"; olive = "808000"; black = "000000"; navy = "000080";
teal = "008080"; silver = "c0c0c0"; maroon = "800000"; gray = "808080";
}
for colorname, rgb in pairs(csscolors) do
stylemap[colorname] = stylemap[colorname] or stylemap[rgb];
colorname, rgb = colorname .. " background", rgb .. " background"
stylemap[colorname] = stylemap[colorname] or stylemap[rgb];
end
local function getstyle(...)
local styles, result = { ... }, {};
for i, style in ipairs(styles) do
style = stylemap[style];
if style then
t_insert(result, style);
end
end
return t_concat(result, ";");
end
local last = "0";
local function setstyle(style)
style = style or "0";
if style ~= last then
io_write("\27["..style.."m");
last = style;
end
end
if windows then
function setstyle(style)
style = style or "0";
if style ~= last then
windows.set_consolecolor(winstylemap[style] or orig_color);
last = style;
end
end
if not orig_color then
function setstyle() end
end
end
local function ansi2css(ansi_codes)
if ansi_codes == "0" then return ""; end
local css = {};
for code in ansi_codes:gmatch("[^;]+") do
t_insert(css, cssmap[tonumber(code)]);
end
return "";
end
local function tohtml(input)
return input:gsub("\027%[(.-)m", ansi2css);
end
return {
getstring = getstring;
getstyle = getstyle;
setstyle = setstyle;
tohtml = tohtml;
};
prosody-0.10.0/util/serialization.lua 0000644 0001750 0001750 00000004640 13163172043 017534 0 ustar matthew matthew -- 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 string_rep = string.rep;
local type = type;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
local pairs = pairs;
local next = next;
local pcall = pcall;
local debug_traceback = debug.traceback;
local log = require "util.logger".init("serialization");
local envload = require"util.envload".envload;
local _ENV = nil;
local indent = function(i)
return string_rep("\t", i);
end
local function basicSerialize (o)
if type(o) == "number" or type(o) == "boolean" then
-- no need to check for NaN, as that's not a valid table index
if o == 1/0 then return "(1/0)";
elseif o == -1/0 then return "(-1/0)";
else return tostring(o); end
else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise.
return (("%q"):format(tostring(o)):gsub("\\\n", "\\n"));
end
end
local function _simplesave(o, ind, t, func)
if type(o) == "number" then
if o ~= o then func(t, "(0/0)");
elseif o == 1/0 then func(t, "(1/0)");
elseif o == -1/0 then func(t, "(-1/0)");
else func(t, tostring(o)); end
elseif type(o) == "string" then
func(t, (("%q"):format(o):gsub("\\\n", "\\n")));
elseif type(o) == "table" then
if next(o) ~= nil then
func(t, "{\n");
for k,v in pairs(o) do
func(t, indent(ind));
func(t, "[");
func(t, basicSerialize(k));
func(t, "] = ");
if ind == 0 then
_simplesave(v, 0, t, func);
else
_simplesave(v, ind+1, t, func);
end
func(t, ";\n");
end
func(t, indent(ind-1));
func(t, "}");
else
func(t, "{}");
end
elseif type(o) == "boolean" then
func(t, (o and "true" or "false"));
else
log("error", "cannot serialize a %s: %s", type(o), debug_traceback())
func(t, "nil");
end
end
local function append(t, o)
_simplesave(o, 1, t, t.write or t_insert);
return t;
end
local function serialize(o)
return t_concat(append({}, o));
end
local function deserialize(str)
if type(str) ~= "string" then return nil; end
str = "return "..str;
local f, err = envload(str, "@data", {});
if not f then return nil, err; end
local success, ret = pcall(f);
if not success then return nil, ret; end
return ret;
end
return {
append = append;
serialize = serialize;
deserialize = deserialize;
};
prosody-0.10.0/util/throttle.lua 0000644 0001750 0001750 00000001701 13163172043 016517 0 ustar matthew matthew
local gettime = require "util.time".now
local setmetatable = setmetatable;
local _ENV = nil;
local throttle = {};
local throttle_mt = { __index = throttle };
function throttle:update()
local newt = gettime();
local elapsed = newt - self.t;
self.t = newt;
local balance = (self.rate * elapsed) + self.balance;
if balance > self.max then
self.balance = self.max;
else
self.balance = balance;
end
return self.balance;
end
function throttle:peek(cost)
cost = cost or 1;
return self.balance >= cost or self:update() >= cost;
end
function throttle:poll(cost, split)
if self:peek(cost) then
self.balance = self.balance - cost;
return true;
else
local balance = self.balance;
if split then
self.balance = 0;
end
return false, balance, (cost-balance);
end
end
local function create(max, period)
return setmetatable({ rate = max / period, max = max, t = gettime(), balance = max }, throttle_mt);
end
return {
create = create;
};
prosody-0.10.0/util/events.lua 0000644 0001750 0001750 00000007775 13163172043 016177 0 ustar matthew matthew -- 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 pairs = pairs;
local t_insert = table.insert;
local t_remove = table.remove;
local t_sort = table.sort;
local setmetatable = setmetatable;
local next = next;
local _ENV = nil;
local function new()
-- Map event name to ordered list of handlers (lazily built): handlers[event_name] = array_of_handler_functions
local handlers = {};
-- Array of wrapper functions that wrap all events (nil if empty)
local global_wrappers;
-- Per-event wrappers: wrappers[event_name] = wrapper_function
local wrappers = {};
-- Event map: event_map[handler_function] = priority_number
local event_map = {};
-- Called on-demand to build handlers entries
local function _rebuild_index(handlers, event)
local _handlers = event_map[event];
if not _handlers or next(_handlers) == nil then return; end
local index = {};
for handler in pairs(_handlers) do
t_insert(index, handler);
end
t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
handlers[event] = index;
return index;
end;
setmetatable(handlers, { __index = _rebuild_index });
local function add_handler(event, handler, priority)
local map = event_map[event];
if map then
map[handler] = priority or 0;
else
map = {[handler] = priority or 0};
event_map[event] = map;
end
handlers[event] = nil;
end;
local function remove_handler(event, handler)
local map = event_map[event];
if map then
map[handler] = nil;
handlers[event] = nil;
if next(map) == nil then
event_map[event] = nil;
end
end
end;
local function get_handlers(event)
return handlers[event];
end;
local function add_handlers(handlers)
for event, handler in pairs(handlers) do
add_handler(event, handler);
end
end;
local function remove_handlers(handlers)
for event, handler in pairs(handlers) do
remove_handler(event, handler);
end
end;
local function _fire_event(event_name, event_data)
local h = handlers[event_name];
if h then
for i=1,#h do
local ret = h[i](event_data);
if ret ~= nil then return ret; end
end
end
end;
local function fire_event(event_name, event_data)
local w = wrappers[event_name] or global_wrappers;
if w then
local curr_wrapper = #w;
local function c(event_name, event_data)
curr_wrapper = curr_wrapper - 1;
if curr_wrapper == 0 then
if global_wrappers == nil or w == global_wrappers then
return _fire_event(event_name, event_data);
end
w, curr_wrapper = global_wrappers, #global_wrappers;
return w[curr_wrapper](c, event_name, event_data);
else
return w[curr_wrapper](c, event_name, event_data);
end
end
return w[curr_wrapper](c, event_name, event_data);
end
return _fire_event(event_name, event_data);
end
local function add_wrapper(event_name, wrapper)
local w;
if event_name == false then
w = global_wrappers;
if not w then
w = {};
global_wrappers = w;
end
else
w = wrappers[event_name];
if not w then
w = {};
wrappers[event_name] = w;
end
end
w[#w+1] = wrapper;
end
local function remove_wrapper(event_name, wrapper)
local w;
if event_name == false then
w = global_wrappers;
else
w = wrappers[event_name];
end
if not w then return; end
for i = #w, 1 do
if w[i] == wrapper then
t_remove(w, i);
end
end
if #w == 0 then
if event_name == false then
global_wrappers = nil;
else
wrappers[event_name] = nil;
end
end
end
return {
add_handler = add_handler;
remove_handler = remove_handler;
add_handlers = add_handlers;
remove_handlers = remove_handlers;
get_handlers = get_handlers;
wrappers = {
add_handler = add_wrapper;
remove_handler = remove_wrapper;
};
add_wrapper = add_wrapper;
remove_wrapper = remove_wrapper;
fire_event = fire_event;
_handlers = handlers;
_event_map = event_map;
};
end
return {
new = new;
};
prosody-0.10.0/util/jid.lua 0000644 0001750 0001750 00000006125 13163172043 015425 0 ustar matthew matthew -- 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 select = select;
local match, sub = string.match, string.sub;
local nodeprep = require "util.encodings".stringprep.nodeprep;
local nameprep = require "util.encodings".stringprep.nameprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
local escapes = {
[" "] = "\\20"; ['"'] = "\\22";
["&"] = "\\26"; ["'"] = "\\27";
["/"] = "\\2f"; [":"] = "\\3a";
["<"] = "\\3c"; [">"] = "\\3e";
["@"] = "\\40"; ["\\"] = "\\5c";
};
local unescapes = {};
for k,v in pairs(escapes) do unescapes[v] = k; end
local _ENV = nil;
local function split(jid)
if not jid then return; end
local node, nodepos = match(jid, "^([^@/]+)@()");
local host, hostpos = match(jid, "^([^@/]+)()", nodepos)
if node and not host then return nil, nil, nil; end
local resource = match(jid, "^/(.+)$", hostpos);
if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end
return node, host, resource;
end
local function bare(jid)
local node, host = split(jid);
if node and host then
return node.."@"..host;
end
return host;
end
local function prepped_split(jid)
local node, host, resource = split(jid);
if host and host ~= "." then
if sub(host, -1, -1) == "." then -- Strip empty root label
host = sub(host, 1, -2);
end
host = nameprep(host);
if not host then return; end
if node then
node = nodeprep(node);
if not node then return; end
end
if resource then
resource = resourceprep(resource);
if not resource then return; end
end
return node, host, resource;
end
end
local function join(node, host, resource)
if not host then return end
if node and resource then
return node.."@"..host.."/"..resource;
elseif node then
return node.."@"..host;
elseif resource then
return host.."/"..resource;
end
return host;
end
local function prep(jid)
local node, host, resource = prepped_split(jid);
return join(node, host, resource);
end
local function compare(jid, acl)
-- compare jid to single acl rule
-- TODO compare to table of rules?
local jid_node, jid_host, jid_resource = split(jid);
local acl_node, acl_host, acl_resource = split(acl);
if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
return true
end
return false
end
local function node(jid)
return (select(1, split(jid)));
end
local function host(jid)
return (select(2, split(jid)));
end
local function resource(jid)
return (select(3, split(jid)));
end
local function escape(s) return s and (s:gsub(".", escapes)); end
local function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
return {
split = split;
bare = bare;
prepped_split = prepped_split;
join = join;
prep = prep;
compare = compare;
node = node;
host = host;
resource = resource;
escape = escape;
unescape = unescape;
};
prosody-0.10.0/util/xmppstream.lua 0000644 0001750 0001750 00000020225 13163172043 017054 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local lxp = require "lxp";
local st = require "util.stanza";
local stanza_mt = st.stanza_mt;
local error = error;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
local t_remove = table.remove;
local setmetatable = setmetatable;
-- COMPAT: w/LuaExpat 1.1.0
local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
local lxp_supports_xmldecl = pcall(lxp.new, { XmlDecl = false });
local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount;
local default_stanza_size_limit = 1024*1024*10; -- 10MB
local _ENV = nil;
local new_parser = lxp.new;
local xml_namespace = {
["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang";
["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space";
["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base";
["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id";
};
local xmlns_streams = "http://etherx.jabber.org/streams";
local ns_separator = "\1";
local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
local function dummy_cb() end
local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
local xml_handlers = {};
local cb_streamopened = stream_callbacks.streamopened;
local cb_streamclosed = stream_callbacks.streamclosed;
local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end;
local cb_handlestanza = stream_callbacks.handlestanza;
cb_handleprogress = cb_handleprogress or dummy_cb;
local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
local stream_tag = stream_callbacks.stream_tag or "stream";
if stream_ns ~= "" then
stream_tag = stream_ns..ns_separator..stream_tag;
end
local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
local stream_default_ns = stream_callbacks.default_ns;
local stack = {};
local chardata, stanza = {};
local stanza_size = 0;
local non_streamns_depth = 0;
function xml_handlers:StartElement(tagname, attr)
if stanza and #chardata > 0 then
-- We have some character data in the buffer
t_insert(stanza, t_concat(chardata));
chardata = {};
end
local curr_ns,name = tagname:match(ns_pattern);
if name == "" then
curr_ns, name = "", curr_ns;
end
if curr_ns ~= stream_default_ns or non_streamns_depth > 0 then
attr.xmlns = curr_ns;
non_streamns_depth = non_streamns_depth + 1;
end
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
local xmlk = xml_namespace[k];
if xmlk then
attr[xmlk] = attr[k];
attr[k] = nil;
end
end
if not stanza then --if we are not currently inside a stanza
if lxp_supports_bytecount then
stanza_size = self:getcurrentbytecount();
end
if session.notopen then
if tagname == stream_tag then
non_streamns_depth = 0;
if cb_streamopened then
if lxp_supports_bytecount then
cb_handleprogress(stanza_size);
stanza_size = 0;
end
cb_streamopened(session, attr);
end
else
-- Garbage before stream?
cb_error(session, "no-stream", tagname);
end
return;
end
if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
cb_error(session, "invalid-top-level-element");
end
stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
else -- we are inside a stanza, so add a tag
if lxp_supports_bytecount then
stanza_size = stanza_size + self:getcurrentbytecount();
end
t_insert(stack, stanza);
local oldstanza = stanza;
stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
t_insert(oldstanza, stanza);
t_insert(oldstanza.tags, stanza);
end
end
if lxp_supports_xmldecl then
function xml_handlers:XmlDecl(version, encoding, standalone)
if lxp_supports_bytecount then
cb_handleprogress(self:getcurrentbytecount());
end
end
end
function xml_handlers:StartCdataSection()
if lxp_supports_bytecount then
if stanza then
stanza_size = stanza_size + self:getcurrentbytecount();
else
cb_handleprogress(self:getcurrentbytecount());
end
end
end
function xml_handlers:EndCdataSection()
if lxp_supports_bytecount then
if stanza then
stanza_size = stanza_size + self:getcurrentbytecount();
else
cb_handleprogress(self:getcurrentbytecount());
end
end
end
function xml_handlers:CharacterData(data)
if stanza then
if lxp_supports_bytecount then
stanza_size = stanza_size + self:getcurrentbytecount();
end
t_insert(chardata, data);
elseif lxp_supports_bytecount then
cb_handleprogress(self:getcurrentbytecount());
end
end
function xml_handlers:EndElement(tagname)
if lxp_supports_bytecount then
stanza_size = stanza_size + self:getcurrentbytecount()
end
if non_streamns_depth > 0 then
non_streamns_depth = non_streamns_depth - 1;
end
if stanza then
if #chardata > 0 then
-- We have some character data in the buffer
t_insert(stanza, t_concat(chardata));
chardata = {};
end
-- Complete stanza
if #stack == 0 then
if lxp_supports_bytecount then
cb_handleprogress(stanza_size);
end
stanza_size = 0;
if tagname ~= stream_error_tag then
cb_handlestanza(session, stanza);
else
cb_error(session, "stream-error", stanza);
end
stanza = nil;
else
stanza = t_remove(stack);
end
else
if cb_streamclosed then
cb_streamclosed(session);
end
end
end
local function restricted_handler(parser)
cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
if not parser.stop or not parser:stop() then
error("Failed to abort parsing");
end
end
if lxp_supports_doctype then
xml_handlers.StartDoctypeDecl = restricted_handler;
end
xml_handlers.Comment = restricted_handler;
xml_handlers.ProcessingInstruction = restricted_handler;
local function reset()
stanza, chardata, stanza_size = nil, {}, 0;
stack = {};
end
local function set_session(stream, new_session)
session = new_session;
end
return xml_handlers, { reset = reset, set_session = set_session };
end
local function new(session, stream_callbacks, stanza_size_limit)
-- Used to track parser progress (e.g. to enforce size limits)
local n_outstanding_bytes = 0;
local handle_progress;
if lxp_supports_bytecount then
function handle_progress(n_parsed_bytes)
n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes;
end
stanza_size_limit = stanza_size_limit or default_stanza_size_limit;
elseif stanza_size_limit then
error("Stanza size limits are not supported on this version of LuaExpat")
end
local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
local parser = new_parser(handlers, ns_separator, false);
local parse = parser.parse;
function session.open_stream(session, from, to)
local send = session.sends2s or session.send;
local attr = {
["xmlns:stream"] = "http://etherx.jabber.org/streams",
["xml:lang"] = "en",
xmlns = stream_callbacks.default_ns,
version = session.version and (session.version > 0 and "1.0" or nil),
id = session.streamid,
from = from or session.host, to = to,
};
if session.stream_attrs then
session:stream_attrs(from, to, attr)
end
send("");
send(st.stanza("stream:stream", attr):top_tag());
return true;
end
return {
reset = function ()
parser = new_parser(handlers, ns_separator, false);
parse = parser.parse;
n_outstanding_bytes = 0;
meta.reset();
end,
feed = function (self, data)
if lxp_supports_bytecount then
n_outstanding_bytes = n_outstanding_bytes + #data;
end
local ok, err = parse(parser, data);
if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then
return nil, "stanza-too-large";
end
return ok, err;
end,
set_session = meta.set_session;
};
end
return {
ns_separator = ns_separator;
ns_pattern = ns_pattern;
new_sax_handlers = new_sax_handlers;
new = new;
};
prosody-0.10.0/util/uuid.lua 0000644 0001750 0001750 00000001501 13163172043 015616 0 ustar matthew matthew -- 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 random = require "util.random";
local random_bytes = random.bytes;
local hex = require "util.hex".to;
local m_ceil = math.ceil;
local function get_nibbles(n)
return hex(random_bytes(m_ceil(n/2))):sub(1, n);
end
local function get_twobits()
return ("%x"):format(random_bytes(1):byte() % 4 + 8);
end
local function generate()
-- generate RFC 4122 complaint UUIDs (version 4 - random)
return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12);
end
return {
get_nibbles=get_nibbles;
generate = generate ;
-- COMPAT
seed = random.seed;
};
prosody-0.10.0/util/dataforms.lua 0000644 0001750 0001750 00000016116 13163172043 016640 0 ustar matthew matthew -- 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 setmetatable = setmetatable;
local ipairs = ipairs;
local tostring, type, next = tostring, type, next;
local t_concat = table.concat;
local st = require "util.stanza";
local jid_prep = require "util.jid".prep;
local _ENV = nil;
local xmlns_forms = 'jabber:x:data';
local form_t = {};
local form_mt = { __index = form_t };
local function new(layout)
return setmetatable(layout, form_mt);
end
function form_t.form(layout, data, formtype)
local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
if layout.title then
form:tag("title"):text(layout.title):up();
end
if layout.instructions then
form:tag("instructions"):text(layout.instructions):up();
end
for _, field in ipairs(layout) do
local field_type = field.type or "text-single";
-- Add field tag
form:tag("field", { type = field_type, var = field.name, label = field.label });
local value = (data and data[field.name]) or field.value;
if value then
-- Add value, depending on type
if field_type == "hidden" then
if type(value) == "table" then
-- Assume an XML snippet
form:tag("value")
:add_child(value)
:up();
else
form:tag("value"):text(tostring(value)):up();
end
elseif field_type == "boolean" then
form:tag("value"):text((value and "1") or "0"):up();
elseif field_type == "fixed" then
form:tag("value"):text(value):up();
elseif field_type == "jid-multi" then
for _, jid in ipairs(value) do
form:tag("value"):text(jid):up();
end
elseif field_type == "jid-single" then
form:tag("value"):text(value):up();
elseif field_type == "text-single" or field_type == "text-private" then
form:tag("value"):text(value):up();
elseif field_type == "text-multi" then
-- Split into multiple tags, one for each line
for line in value:gmatch("([^\r\n]+)\r?\n*") do
form:tag("value"):text(line):up();
end
elseif field_type == "list-single" then
if formtype ~= "result" then
local has_default = false;
for _, val in ipairs(field.options or value) do
if type(val) == "table" then
form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
if value == val.value or val.default and (not has_default) then
form:tag("value"):text(val.value):up();
has_default = true;
end
else
form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
end
end
end
if (field.options or formtype == "result") and value then
form:tag("value"):text(value):up();
end
elseif field_type == "list-multi" then
if formtype ~= "result" then
for _, val in ipairs(field.options or value) do
if type(val) == "table" then
form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
if not field.options and val.default then
form:tag("value"):text(val.value):up();
end
else
form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
end
end
end
if (field.options or formtype == "result") and value then
for _, val in ipairs(value) do
form:tag("value"):text(val):up();
end
end
end
end
local media = field.media;
if media then
form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
for _, val in ipairs(media) do
form:tag("uri", { type = val.type }):text(val.uri):up()
end
form:up();
end
if field.required then
form:tag("required"):up();
end
-- Jump back up to list of fields
form:up();
end
return form;
end
local field_readers = {};
function form_t.data(layout, stanza)
local data = {};
local errors = {};
local present = {};
for _, field in ipairs(layout) do
local tag;
for field_tag in stanza:childtags("field") do
if field.name == field_tag.attr.var then
tag = field_tag;
break;
end
end
if not tag then
if field.required then
errors[field.name] = "Required value missing";
end
else
present[field.name] = true;
local reader = field_readers[field.type];
if reader then
data[field.name], errors[field.name] = reader(tag, field.required);
end
end
end
if next(errors) then
return data, errors, present;
end
return data, nil, present;
end
local function simple_text(field_tag, required)
local data = field_tag:get_child_text("value");
-- XEP-0004 does not say if an empty string is acceptable for a required value
-- so we will follow HTML5 which says that empty string means missing
if required and (data == nil or data == "") then
return nil, "Required value missing";
end
return data; -- Return whatever get_child_text returned, even if empty string
end
field_readers["text-single"] = simple_text;
field_readers["text-private"] = simple_text;
field_readers["jid-single"] =
function (field_tag, required)
local raw_data, err = simple_text(field_tag, required);
if not raw_data then return raw_data, err; end
local data = jid_prep(raw_data);
if not data then
return nil, "Invalid JID: " .. raw_data;
end
return data;
end
field_readers["jid-multi"] =
function (field_tag, required)
local result = {};
local err = {};
for value_tag in field_tag:childtags("value") do
local raw_value = value_tag:get_text();
local value = jid_prep(raw_value);
result[#result+1] = value;
if raw_value and not value then
err[#err+1] = ("Invalid JID: " .. raw_value);
end
end
if #result > 0 then
return result, (#err > 0 and t_concat(err, "\n") or nil);
elseif required then
return nil, "Required value missing";
end
end
field_readers["list-multi"] =
function (field_tag, required)
local result = {};
for value in field_tag:childtags("value") do
result[#result+1] = value:get_text();
end
if #result > 0 then
return result;
elseif required then
return nil, "Required value missing";
end
end
field_readers["text-multi"] =
function (field_tag, required)
local data, err = field_readers["list-multi"](field_tag, required);
if data then
data = t_concat(data, "\n");
end
return data, err;
end
field_readers["list-single"] = simple_text;
local boolean_values = {
["1"] = true, ["true"] = true,
["0"] = false, ["false"] = false,
};
field_readers["boolean"] =
function (field_tag, required)
local raw_value, err = simple_text(field_tag, required);
if not raw_value then return raw_value, err; end
local value = boolean_values[raw_value];
if value == nil then
return nil, "Invalid boolean representation:" .. raw_value;
end
return value;
end
field_readers["hidden"] =
function (field_tag)
return field_tag:get_child_text("value");
end
return {
new = new;
};
--[=[
Layout:
{
title = "MUC Configuration",
instructions = [[Use this form to configure options for this MUC room.]],
{ name = "FORM_TYPE", type = "hidden", required = true };
{ name = "field-name", type = "field-type", required = false };
}
--]=]
prosody-0.10.0/util/ip.lua 0000644 0001750 0001750 00000014636 13163172043 015275 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2011 Florian Zeitz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local ip_methods = {};
local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
__tostring = function (ip) return ip.addr; end,
__eq = function (ipA, ipB) return ipA.addr == ipB.addr; end};
local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
local function new_ip(ipStr, proto)
if not proto then
local sep = ipStr:match("^%x+(.)");
if sep == ":" or (not(sep) and ipStr:sub(1,1) == ":") then
proto = "IPv6"
elseif sep == "." then
proto = "IPv4"
end
if not proto then
return nil, "invalid address";
end
elseif proto ~= "IPv4" and proto ~= "IPv6" then
return nil, "invalid protocol";
end
local zone;
if proto == "IPv6" and ipStr:find('%', 1, true) then
ipStr, zone = ipStr:match("^(.-)%%(.*)");
end
if proto == "IPv6" and ipStr:find('.', 1, true) then
local changed;
ipStr, changed = ipStr:gsub(":(%d+)%.(%d+)%.(%d+)%.(%d+)$", function(a,b,c,d)
return (":%04X:%04X"):format(a*256+b,c*256+d);
end);
if changed ~= 1 then return nil, "invalid-address"; end
end
return setmetatable({ addr = ipStr, proto = proto, zone = zone }, ip_mt);
end
local function toBits(ip)
local result = "";
local fields = {};
if ip.proto == "IPv4" then
ip = ip.toV4mapped;
end
ip = (ip.addr):upper();
ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
if not ip:match(":$") then fields[#fields] = nil; end
for i, field in ipairs(fields) do
if field:len() == 0 and i ~= 1 and i ~= #fields then
for _ = 1, 16 * (9 - #fields) do
result = result .. "0";
end
else
for _ = 1, 4 - field:len() do
result = result .. "0000";
end
for j = 1, field:len() do
result = result .. hex2bits[field:sub(j, j)];
end
end
end
return result;
end
local function commonPrefixLength(ipA, ipB)
ipA, ipB = toBits(ipA), toBits(ipB);
for i = 1, 128 do
if ipA:sub(i,i) ~= ipB:sub(i,i) then
return i-1;
end
end
return 128;
end
local function v4scope(ip)
local fields = {};
ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
-- Loopback:
if fields[1] == 127 then
return 0x2;
-- Link-local unicast:
elseif fields[1] == 169 and fields[2] == 254 then
return 0x2;
-- Global unicast:
else
return 0xE;
end
end
local function v6scope(ip)
-- Loopback:
if ip:match("^[0:]*1$") then
return 0x2;
-- Link-local unicast:
elseif ip:match("^[Ff][Ee][89ABab]") then
return 0x2;
-- Site-local unicast:
elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
return 0x5;
-- Multicast:
elseif ip:match("^[Ff][Ff]") then
return tonumber("0x"..ip:sub(4,4));
-- Global unicast:
else
return 0xE;
end
end
local function label(ip)
if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
return 0;
elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
return 2;
elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
return 5;
elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
return 13;
elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
return 11;
elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
return 12;
elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
return 3;
elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
return 4;
else
return 1;
end
end
local function precedence(ip)
if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
return 50;
elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
return 30;
elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
return 5;
elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
return 3;
elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
return 1;
elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
return 1;
elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
return 1;
elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
return 35;
else
return 40;
end
end
local function toV4mapped(ip)
local fields = {};
local ret = "::ffff:";
ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
ret = ret .. ("%02x"):format(fields[1]);
ret = ret .. ("%02x"):format(fields[2]);
ret = ret .. ":"
ret = ret .. ("%02x"):format(fields[3]);
ret = ret .. ("%02x"):format(fields[4]);
return new_ip(ret, "IPv6");
end
function ip_methods:toV4mapped()
if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
local value = toV4mapped(self.addr);
self.toV4mapped = value;
return value;
end
function ip_methods:label()
local value;
if self.proto == "IPv4" then
value = label(self.toV4mapped);
else
value = label(self);
end
self.label = value;
return value;
end
function ip_methods:precedence()
local value;
if self.proto == "IPv4" then
value = precedence(self.toV4mapped);
else
value = precedence(self);
end
self.precedence = value;
return value;
end
function ip_methods:scope()
local value;
if self.proto == "IPv4" then
value = v4scope(self.addr);
else
value = v6scope(self.addr);
end
self.scope = value;
return value;
end
function ip_methods:private()
local private = self.scope ~= 0xE;
if not private and self.proto == "IPv4" then
local ip = self.addr;
local fields = {};
ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
if fields[1] == 127 or fields[1] == 10 or (fields[1] == 192 and fields[2] == 168)
or (fields[1] == 172 and (fields[2] >= 16 or fields[2] <= 32)) then
private = true;
end
end
self.private = private;
return private;
end
local function parse_cidr(cidr)
local bits;
local ip_len = cidr:find("/", 1, true);
if ip_len then
bits = tonumber(cidr:sub(ip_len+1, -1));
cidr = cidr:sub(1, ip_len-1);
end
return new_ip(cidr), bits;
end
local function match(ipA, ipB, bits)
local common_bits = commonPrefixLength(ipA, ipB);
if bits and ipB.proto == "IPv4" then
common_bits = common_bits - 96; -- v6 mapped addresses always share these bits
end
return common_bits >= (bits or 128);
end
return {new_ip = new_ip,
commonPrefixLength = commonPrefixLength,
parse_cidr = parse_cidr,
match=match};
prosody-0.10.0/util/paths.lua 0000644 0001750 0001750 00000002023 13163172043 015767 0 ustar matthew matthew local t_concat = table.concat;
local path_sep = package.config:sub(1,1);
local path_util = {}
-- Helper function to resolve relative paths (needed by config)
function path_util.resolve_relative_path(parent_path, path)
if path then
-- Some normalization
parent_path = parent_path:gsub("%"..path_sep.."+$", "");
path = path:gsub("^%.%"..path_sep.."+", "");
local is_relative;
if path_sep == "/" and path:sub(1,1) ~= "/" then
is_relative = true;
elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
is_relative = true;
end
if is_relative then
return parent_path..path_sep..path;
end
end
return path;
end
-- Helper function to convert a glob to a Lua pattern
function path_util.glob_to_pattern(glob)
return "^"..glob:gsub("[%p*?]", function (c)
if c == "*" then
return ".*";
elseif c == "?" then
return ".";
else
return "%"..c;
end
end).."$";
end
function path_util.join(...)
return t_concat({...}, path_sep);
end
return path_util;
prosody-0.10.0/util/interpolation.lua 0000644 0001750 0001750 00000005750 13163172043 017551 0 ustar matthew matthew -- Simple template language
--
-- The new() function takes a pattern and an escape function and returns
-- a render() function. Both are required.
--
-- The function render() takes a string template and a table of values.
-- Sequences like {name} in the template string are substituted
-- with values from the table, optionally depending on a modifier
-- symbol.
--
-- Variants are:
-- {name} is substituted for values["name"] and is escaped using the
-- second argument to new_render(). To disable the escaping, use {name!}.
-- {name.item} can be used to access table items.
-- To renter lists of items: {name# item number {idx} is {item} }
-- Or key-value pairs: {name% t[ {idx} ] = {item} }
-- To show a defaults for missing values {name? sub-template } can be used,
-- which renders a sub-template if values["name"] is false-ish.
-- {name& sub-template } does the opposite, the sub-template is rendered
-- if the selected value is anything but false or nil.
local type, tostring = type, tostring;
local pairs, ipairs = pairs, ipairs;
local s_sub, s_gsub, s_match = string.sub, string.gsub, string.match;
local t_concat = table.concat;
local function new_render(pat, escape, funcs)
-- assert(type(pat) == "string", "bad argument #1 to 'new_render' (string expected)");
-- assert(type(escape) == "function", "bad argument #2 to 'new_render' (function expected)");
local function render(template, values)
-- assert(type(template) == "string", "bad argument #1 to 'render' (string expected)");
-- assert(type(values) == "table", "bad argument #2 to 'render' (table expected)");
return (s_gsub(template, pat, function (block)
block = s_sub(block, 2, -2);
local name, opt, e = s_match(block, "^([%a_][%w_.]*)(%p?)()");
if not name then return end
local value = values[name];
if not value and name:find(".", 2, true) then
value = values;
for word in name:gmatch"[^.]+" do
value = value[word];
if not value then break; end
end
end
if funcs then
while value ~= nil and opt == '|' do
local f;
f, opt, e = s_match(block, "^([%a_][%w_.]*)(%p?)()", e);
f = funcs[f];
if f then value = f(value); end
end
end
if opt == '#' or opt == '%' then
if type(value) ~= "table" then return ""; end
local iter = opt == '#' and ipairs or pairs;
local out, i, subtpl = {}, 1, s_sub(block, e);
local subvalues = setmetatable({}, { __index = values });
for idx, item in iter(value) do
subvalues.idx = idx;
subvalues.item = item;
out[i], i = render(subtpl, subvalues), i+1;
end
return t_concat(out);
elseif opt == '&' then
if not value then return ""; end
return render(s_sub(block, e), values);
elseif opt == '?' and not value then
return render(s_sub(block, e), values);
elseif value ~= nil then
if type(value) ~= "string" then
value = tostring(value);
end
if opt ~= '!' then
return escape(value);
end
return value;
end
end));
end
return render;
end
return {
new = new_render;
};
prosody-0.10.0/util/cache.lua 0000644 0001750 0001750 00000005745 13163172043 015731 0 ustar matthew matthew
local function _remove(list, m)
if m.prev then
m.prev.next = m.next;
end
if m.next then
m.next.prev = m.prev;
end
if list._tail == m then
list._tail = m.prev;
end
if list._head == m then
list._head = m.next;
end
list._count = list._count - 1;
end
local function _insert(list, m)
if list._head then
list._head.prev = m;
end
m.prev, m.next = nil, list._head;
list._head = m;
if not list._tail then
list._tail = m;
end
list._count = list._count + 1;
end
local cache_methods = {};
local cache_mt = { __index = cache_methods };
function cache_methods:set(k, v)
local m = self._data[k];
if m then
-- Key already exists
if v ~= nil then
-- Bump to head of list
_remove(self, m);
_insert(self, m);
m.value = v;
else
-- Remove from list
_remove(self, m);
self._data[k] = nil;
end
return true;
end
-- New key
if v == nil then
return true;
end
-- Check whether we need to remove oldest k/v
if self._count == self.size then
local tail = self._tail;
local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value) == false) then
-- Cache is full, and we're not allowed to evict
return false;
end
_remove(self, tail);
self._data[evicted_key] = nil;
end
m = { key = k, value = v, prev = nil, next = nil };
self._data[k] = m;
_insert(self, m);
return true;
end
function cache_methods:get(k)
local m = self._data[k];
if m then
return m.value;
end
return nil;
end
function cache_methods:items()
local m = self._head;
return function ()
if not m then
return;
end
local k, v = m.key, m.value;
m = m.next;
return k, v;
end
end
function cache_methods:values()
local m = self._head;
return function ()
if not m then
return;
end
local v = m.value;
m = m.next;
return v;
end
end
function cache_methods:count()
return self._count;
end
function cache_methods:head()
local head = self._head;
if not head then return nil, nil; end
return head.key, head.value;
end
function cache_methods:tail()
local tail = self._tail;
if not tail then return nil, nil; end
return tail.key, tail.value;
end
function cache_methods:table()
--luacheck: ignore 212/t
if not self.proxy_table then
self.proxy_table = setmetatable({}, {
__index = function (t, k)
return self:get(k);
end;
__newindex = function (t, k, v)
if not self:set(k, v) then
error("failed to insert key into cache - full");
end
end;
__pairs = function (t)
return self:items();
end;
__len = function (t)
return self:count();
end;
});
end
return self.proxy_table;
end
local function new(size, on_evict)
size = assert(tonumber(size), "cache size must be a number");
size = math.floor(size);
assert(size > 0, "cache size must be greater than zero");
local data = {};
return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt);
end
return {
new = new;
}
prosody-0.10.0/util/import.lua 0000644 0001750 0001750 00000001043 13163172043 016163 0 ustar matthew matthew -- 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 unpack = table.unpack or unpack; --luacheck: ignore 113
local t_insert = table.insert;
function import(module, ...)
local m = package.loaded[module] or require(module);
if type(m) == "table" and ... then
local ret = {};
for _, f in ipairs{...} do
t_insert(ret, m[f]);
end
return unpack(ret);
end
return m;
end
prosody-0.10.0/util/watchdog.lua 0000644 0001750 0001750 00000001457 13163172043 016462 0 ustar matthew matthew local timer = require "util.timer";
local setmetatable = setmetatable;
local os_time = os.time;
local _ENV = nil;
local watchdog_methods = {};
local watchdog_mt = { __index = watchdog_methods };
local function new(timeout, callback)
local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt);
timer.add_task(timeout+1, function (current_time)
local last_reset = watchdog.last_reset;
if not last_reset then
return;
end
local time_left = (last_reset + timeout) - current_time;
if time_left < 0 then
return watchdog:callback();
end
return time_left + 1;
end);
return watchdog;
end
function watchdog_methods:reset()
self.last_reset = os_time();
end
function watchdog_methods:cancel()
self.last_reset = nil;
end
return {
new = new;
};
prosody-0.10.0/util/sslconfig.lua 0000644 0001750 0001750 00000005734 13163172043 016653 0 ustar matthew matthew -- util to easily merge multiple sets of LuaSec context options
local type = type;
local pairs = pairs;
local rawset = rawset;
local t_concat = table.concat;
local t_insert = table.insert;
local setmetatable = setmetatable;
local _ENV = nil;
local handlers = { };
local finalisers = { };
local id = function (v) return v end
-- All "handlers" behave like extended rawset(table, key, value) with extra
-- processing usually merging the new value with the old in some reasonable
-- way
-- If a field does not have a defined handler then a new value simply
-- replaces the old.
-- Convert either a list or a set into a special type of set where each
-- item is either positive or negative in order for a later set of options
-- to be able to remove options from this set by filtering out the negative ones
function handlers.options(config, field, new)
local options = config[field] or { };
if type(new) ~= "table" then new = { new } end
for key, value in pairs(new) do
if value == true or value == false then
options[key] = value;
else -- list item
options[value] = true;
end
end
config[field] = options;
end
handlers.verifyext = handlers.options;
-- finalisers take something produced by handlers and return what luasec
-- expects it to be
-- Produce a list of "positive" options from the set
function finalisers.options(options)
local output = {};
for opt, enable in pairs(options) do
if enable then
output[#output+1] = opt;
end
end
return output;
end
finalisers.verifyext = finalisers.options;
-- We allow ciphers to be a list
function finalisers.ciphers(cipherlist)
if type(cipherlist) == "table" then
return t_concat(cipherlist, ":");
end
return cipherlist;
end
-- Curve list too
finalisers.curveslist = finalisers.ciphers;
-- protocol = "x" should enable only that protocol
-- protocol = "x+" should enable x and later versions
local protocols = { "sslv2", "sslv3", "tlsv1", "tlsv1_1", "tlsv1_2" };
for i = 1, #protocols do protocols[protocols[i] .. "+"] = i - 1; end
-- this interacts with ssl.options as well to add no_x
local function protocol(config)
local min_protocol = protocols[config.protocol];
if min_protocol then
config.protocol = "sslv23";
for i = 1, min_protocol do
t_insert(config.options, "no_"..protocols[i]);
end
end
end
-- Merge options from 'new' config into 'config'
local function apply(config, new)
if type(new) == "table" then
for field, value in pairs(new) do
(handlers[field] or rawset)(config, field, value);
end
end
end
-- Finalize the config into the form LuaSec expects
local function final(config)
local output = { };
for field, value in pairs(config) do
output[field] = (finalisers[field] or id)(value);
end
-- Need to handle protocols last because it adds to the options list
protocol(output);
return output;
end
local sslopts_mt = {
__index = {
apply = apply;
final = final;
};
};
local function new()
return setmetatable({options={}}, sslopts_mt);
end
return {
apply = apply;
final = final;
new = new;
};
prosody-0.10.0/util/x509.lua 0000644 0001750 0001750 00000016152 13163172043 015365 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2010 Matthew Wild
-- Copyright (C) 2010 Paul Aurich
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- TODO: I feel a fair amount of this logic should be integrated into Luasec,
-- so that everyone isn't re-inventing the wheel. Dependencies on
-- IDN libraries complicate that.
-- [TLS-CERTS] - http://tools.ietf.org/html/rfc6125
-- [XMPP-CORE] - http://tools.ietf.org/html/rfc6120
-- [SRV-ID] - http://tools.ietf.org/html/rfc4985
-- [IDNA] - http://tools.ietf.org/html/rfc5890
-- [LDAP] - http://tools.ietf.org/html/rfc4519
-- [PKIX] - http://tools.ietf.org/html/rfc5280
local nameprep = require "util.encodings".stringprep.nameprep;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local base64 = require "util.encodings".base64;
local log = require "util.logger".init("x509");
local s_format = string.format;
local _ENV = nil;
local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3
local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6
local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE]
local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID]
-- Compare a hostname (possibly international) with asserted names
-- extracted from a certificate.
-- This function follows the rules laid out in
-- sections 6.4.1 and 6.4.2 of [TLS-CERTS]
--
-- A wildcard ("*") all by itself is allowed only as the left-most label
local function compare_dnsname(host, asserted_names)
-- TODO: Sufficient normalization? Review relevant specs.
local norm_host = idna_to_ascii(host)
if norm_host == nil then
log("info", "Host %s failed IDNA ToASCII operation", host)
return false
end
norm_host = norm_host:lower()
local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label
for i=1,#asserted_names do
local name = asserted_names[i]
if norm_host == name:lower() then
log("debug", "Cert dNSName %s matched hostname", name);
return true
end
-- Allow the left most label to be a "*"
if name:match("^%*%.") then
local rest_name = name:gsub("^[^.]+%.", "")
if host_chopped == rest_name:lower() then
log("debug", "Cert dNSName %s matched hostname", name);
return true
end
end
end
return false
end
-- Compare an XMPP domain name with the asserted id-on-xmppAddr
-- identities extracted from a certificate. Both are UTF8 strings.
--
-- Per [XMPP-CORE], matches against asserted identities don't include
-- wildcards, so we just do a normalize on both and then a string comparison
--
-- TODO: Support for full JIDs?
local function compare_xmppaddr(host, asserted_names)
local norm_host = nameprep(host)
for i=1,#asserted_names do
local name = asserted_names[i]
-- We only want to match against bare domains right now, not
-- those crazy full-er JIDs.
if name:match("[@/]") then
log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name)
else
local norm_name = nameprep(name)
if norm_name == nil then
log("info", "Ignoring xmppAddr %s, failed nameprep!", name)
else
if norm_host == norm_name then
log("debug", "Cert xmppAddr %s matched hostname", name)
return true
end
end
end
end
return false
end
-- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID)
-- identities extracted from a certificate.
--
-- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII.
-- Comparison is done case-insensitively, and a wildcard ("*") all by itself
-- is allowed only as the left-most non-service label.
local function compare_srvname(host, service, asserted_names)
local norm_host = idna_to_ascii(host)
if norm_host == nil then
log("info", "Host %s failed IDNA ToASCII operation", host);
return false
end
-- Service names start with a "_"
if service:match("^_") == nil then service = "_"..service end
norm_host = norm_host:lower();
local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label
for i=1,#asserted_names do
local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)");
if service == asserted_service then
if norm_host == name:lower() then
log("debug", "Cert SRVName %s matched hostname", name);
return true;
end
-- Allow the left most label to be a "*"
if name:match("^%*%.") then
local rest_name = name:gsub("^[^.]+%.", "")
if host_chopped == rest_name:lower() then
log("debug", "Cert SRVName %s matched hostname", name)
return true
end
end
if norm_host == name:lower() then
log("debug", "Cert SRVName %s matched hostname", name);
return true
end
end
end
return false
end
local function verify_identity(host, service, cert)
if cert.setencode then
cert:setencode("utf8");
end
local ext = cert:extensions()
if ext[oid_subjectaltname] then
local sans = ext[oid_subjectaltname];
-- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a
-- reference identifier if the presented identifiers include a DNS-ID
-- SRV-ID, URI-ID, or any application-specific identifier types"
local had_supported_altnames = false
if sans[oid_xmppaddr] then
had_supported_altnames = true
if service == "_xmpp-client" or service == "_xmpp-server" then
if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end
end
end
if sans[oid_dnssrv] then
had_supported_altnames = true
-- Only check srvNames if the caller specified a service
if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end
end
if sans["dNSName"] then
had_supported_altnames = true
if compare_dnsname(host, sans["dNSName"]) then return true end
end
-- We don't need URIs, but [TLS-CERTS] is clear.
if sans["uniformResourceIdentifier"] then
had_supported_altnames = true
end
if had_supported_altnames then return false end
end
-- Extract a common name from the certificate, and check it as if it were
-- a dNSName subjectAltName (wildcards may apply for, and receive,
-- cat treats)
--
-- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject
-- which has one and only one Common Name
local subject = cert:subject()
local cn = nil
for i=1,#subject do
local dn = subject[i]
if dn["oid"] == oid_commonname then
if cn then
log("info", "Certificate has multiple common names")
return false
end
cn = dn["value"];
end
end
if cn then
-- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs.
return compare_dnsname(host, { cn })
end
-- If all else fails, well, why should we be any different?
return false
end
local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
local function pem2der(pem)
local typ, data = pem:match(pat);
if typ and data then
return base64.decode(data), typ;
end
end
local wrap = ('.'):rep(64);
local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
local function der2pem(data, typ)
typ = typ and typ:upper() or "CERTIFICATE";
data = base64.encode(data);
return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ);
end
return {
verify_identity = verify_identity;
pem2der = pem2der;
der2pem = der2pem;
};
prosody-0.10.0/util/envload.lua 0000644 0001750 0001750 00000002134 13163172043 016303 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2011 Florian Zeitz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- luacheck: ignore 113/setfenv
local load, loadstring, setfenv = load, loadstring, setfenv;
local io_open = io.open;
local envload;
local envloadfile;
if setfenv then
function envload(code, source, env)
local f, err = loadstring(code, source);
if f and env then setfenv(f, env); end
return f, err;
end
function envloadfile(file, env)
local fh, err, errno = io_open(file);
if not fh then return fh, err, errno; end
local f, err = load(function () return fh:read(2048); end, "@"..file);
fh:close();
if f and env then setfenv(f, env); end
return f, err;
end
else
function envload(code, source, env)
return load(code, source, nil, env);
end
function envloadfile(file, env)
local fh, err, errno = io_open(file);
if not fh then return fh, err, errno; end
local f, err = load(fh:lines(2048), "@"..file, nil, env);
fh:close();
return f, err;
end
end
return { envload = envload, envloadfile = envloadfile };
prosody-0.10.0/util/helpers.lua 0000644 0001750 0001750 00000004737 13163172043 016330 0 ustar matthew matthew -- 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 debug = require "util.debug";
-- Helper functions for debugging
local log = require "util.logger".init("util.debug");
local function log_events(events, name, logger)
local f = events.fire_event;
if not f then
error("Object does not appear to be a util.events object");
end
logger = logger or log;
name = name or tostring(events);
function events.fire_event(event, ...)
logger("debug", "%s firing event: %s", name, event);
return f(event, ...);
end
events[events.fire_event] = f;
return events;
end
local function revert_log_events(events)
events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :))
end
local function log_host_events(host)
return log_events(prosody.hosts[host].events, host);
end
local function revert_log_host_events(host)
return revert_log_events(prosody.hosts[host].events);
end
local function show_events(events, specific_event)
local event_handlers = events._handlers;
local events_array = {};
local event_handler_arrays = {};
for event, priorities in pairs(events._event_map) do
local handlers = event_handlers[event];
if handlers and (event == specific_event or not specific_event) then
table.insert(events_array, event);
local handler_strings = {};
for i, handler in ipairs(handlers) do
local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler));
handler_strings[i] = " "..priorities[handler]..": "..tostring(handler)..(upvals and ("\n "..upvals) or "");
end
event_handler_arrays[event] = handler_strings;
end
end
table.sort(events_array);
local i = 1;
while i <= #events_array do
local handlers = event_handler_arrays[events_array[i]];
for j=#handlers, 1, -1 do
table.insert(events_array, i+1, handlers[j]);
end
if i > 1 then events_array[i] = "\n"..events_array[i]; end
i = i + #handlers + 1
end
return table.concat(events_array, "\n");
end
local function get_upvalue(f, get_name)
local i, name, value = 0;
repeat
i = i + 1;
name, value = debug.getupvalue(f, i);
until name == get_name or name == nil;
return value;
end
return {
log_host_events = log_host_events;
revert_log_host_events = revert_log_host_events;
log_events = log_events;
revert_log_events = revert_log_events;
show_events = show_events;
get_upvalue = get_upvalue;
};
prosody-0.10.0/util/presence.lua 0000644 0001750 0001750 00000001725 13163172043 016464 0 ustar matthew matthew -- 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 t_insert = table.insert;
local function select_top_resources(user)
local priority = 0;
local recipients = {};
for _, session in pairs(user.sessions) do -- find resource with greatest priority
if session.presence then
-- TODO check active privacy list for session
local p = session.priority;
if p > priority then
priority = p;
recipients = {session};
elseif p == priority then
t_insert(recipients, session);
end
end
end
return recipients;
end
local function recalc_resource_map(user)
if user then
user.top_resources = select_top_resources(user);
if #user.top_resources == 0 then user.top_resources = nil; end
end
end
return {
select_top_resources = select_top_resources;
recalc_resource_map = recalc_resource_map;
}
prosody-0.10.0/util/pluginloader.lua 0000644 0001750 0001750 00000004265 13163172043 017347 0 ustar matthew matthew -- 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 dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)");
local plugin_dir = {};
for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do
path = path..dir_sep; -- add path separator to path end
path = path:gsub(dir_sep..dir_sep.."+", dir_sep); -- coalesce multiple separaters
plugin_dir[#plugin_dir + 1] = path;
end
local io_open = io.open;
local envload = require "util.envload".envload;
local function load_file(names)
local file, err, path;
for i=1,#plugin_dir do
for j=1,#names do
path = plugin_dir[i]..names[j];
file, err = io_open(path);
if file then
local content = file:read("*a");
file:close();
return content, path;
end
end
end
return file, err;
end
local function load_resource(plugin, resource)
resource = resource or "mod_"..plugin..".lua";
local names = {
"mod_"..plugin..dir_sep..plugin..dir_sep..resource; -- mod_hello/hello/mod_hello.lua
"mod_"..plugin..dir_sep..resource; -- mod_hello/mod_hello.lua
plugin..dir_sep..resource; -- hello/mod_hello.lua
resource; -- mod_hello.lua
};
return load_file(names);
end
local function load_code(plugin, resource, env)
local content, err = load_resource(plugin, resource);
if not content then return content, err; end
local path = err;
local f, err = envload(content, "@"..path, env);
if not f then return f, err; end
return f, path;
end
local function load_code_ext(plugin, resource, extension, env)
local content, err = load_resource(plugin, resource.."."..extension);
if not content then
content, err = load_resource(resource, resource.."."..extension);
if not content then
return content, err;
end
end
local path = err;
local f, err = envload(content, "@"..path, env);
if not f then return f, err; end
return f, path;
end
return {
load_file = load_file;
load_resource = load_resource;
load_code = load_code;
load_code_ext = load_code_ext;
};
prosody-0.10.0/util/id.lua 0000644 0001750 0001750 00000001456 13163172043 015255 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2008-2017 Matthew Wild
-- Copyright (C) 2008-2017 Waqas Hussain
-- Copyright (C) 2008-2017 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local s_gsub = string.gsub;
local random_bytes = require "util.random".bytes;
local base64_encode = require "util.encodings".base64.encode;
local b64url = { ["+"] = "-", ["/"] = "_", ["="] = "" };
local function b64url_random(len)
return (s_gsub(base64_encode(random_bytes(len)), "[+/=]", b64url));
end
return {
short = function () return b64url_random(6); end;
medium = function () return b64url_random(12); end;
long = function () return b64url_random(24); end;
custom = function (size)
return function () return b64url_random(size); end;
end;
}
prosody-0.10.0/util/logger.lua 0000644 0001750 0001750 00000003265 13163172043 016140 0 ustar matthew matthew -- 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.
--
-- luacheck: ignore 213/level
local pairs = pairs;
local _ENV = nil;
local level_sinks = {};
local make_logger;
local function init(name)
local log_debug = make_logger(name, "debug");
local log_info = make_logger(name, "info");
local log_warn = make_logger(name, "warn");
local log_error = make_logger(name, "error");
return function (level, message, ...)
if level == "debug" then
return log_debug(message, ...);
elseif level == "info" then
return log_info(message, ...);
elseif level == "warn" then
return log_warn(message, ...);
elseif level == "error" then
return log_error(message, ...);
end
end
end
function make_logger(source_name, level)
local level_handlers = level_sinks[level];
if not level_handlers then
level_handlers = {};
level_sinks[level] = level_handlers;
end
local logger = function (message, ...)
for i = 1,#level_handlers do
level_handlers[i](source_name, level, message, ...);
end
end
return logger;
end
local function reset()
for level, handler_list in pairs(level_sinks) do
-- Clear all handlers for this level
for i = 1, #handler_list do
handler_list[i] = nil;
end
end
end
local function add_level_sink(level, sink_function)
if not level_sinks[level] then
level_sinks[level] = { sink_function };
else
level_sinks[level][#level_sinks[level] + 1 ] = sink_function;
end
end
return {
init = init;
make_logger = make_logger;
reset = reset;
add_level_sink = add_level_sink;
new = make_logger;
};
prosody-0.10.0/util/pubsub.lua 0000644 0001750 0001750 00000026034 13163172043 016160 0 ustar matthew matthew local events = require "util.events";
local cache = require "util.cache";
local service = {};
local service_mt = { __index = service };
local default_config = { __index = {
itemstore = function (config) return cache.new(tonumber(config["pubsub#max_items"])) end;
broadcaster = function () end;
get_affiliation = function () end;
capabilities = {};
} };
local default_node_config = { __index = {
["pubsub#max_items"] = "20";
} };
local function new(config)
config = config or {};
return setmetatable({
config = setmetatable(config, default_config);
node_defaults = setmetatable(config.node_defaults or {}, default_node_config);
affiliations = {};
subscriptions = {};
nodes = {};
data = {};
events = events.new();
}, service_mt);
end
function service:jids_equal(jid1, jid2)
local normalize = self.config.normalize_jid;
return normalize(jid1) == normalize(jid2);
end
function service:may(node, actor, action)
if actor == true then return true; end
local node_obj = self.nodes[node];
local node_aff = node_obj and node_obj.affiliations[actor];
local service_aff = self.affiliations[actor]
or self.config.get_affiliation(actor, node, action)
or "none";
-- Check if node allows/forbids it
local node_capabilities = node_obj and node_obj.capabilities;
if node_capabilities then
local caps = node_capabilities[node_aff or service_aff];
if caps then
local can = caps[action];
if can ~= nil then
return can;
end
end
end
-- Check service-wide capabilities instead
local service_capabilities = self.config.capabilities;
local caps = service_capabilities[node_aff or service_aff];
if caps then
local can = caps[action];
if can ~= nil then
return can;
end
end
return false;
end
function service:set_affiliation(node, actor, jid, affiliation)
-- Access checking
if not self:may(node, actor, "set_affiliation") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
node_obj.affiliations[jid] = affiliation;
local _, jid_sub = self:get_subscription(node, true, jid);
if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
local ok, err = self:add_subscription(node, true, jid);
if not ok then
return ok, err;
end
elseif jid_sub and not self:may(node, jid, "be_subscribed") then
local ok, err = self:add_subscription(node, true, jid);
if not ok then
return ok, err;
end
end
return true;
end
function service:add_subscription(node, actor, jid, options)
-- Access checking
local cap;
if actor == true or jid == actor or self:jids_equal(actor, jid) then
cap = "subscribe";
else
cap = "subscribe_other";
end
if not self:may(node, actor, cap) then
return false, "forbidden";
end
if not self:may(node, jid, "be_subscribed") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
if not self.config.autocreate_on_subscribe then
return false, "item-not-found";
else
local ok, err = self:create(node, true);
if not ok then
return ok, err;
end
node_obj = self.nodes[node];
end
end
node_obj.subscribers[jid] = options or true;
local normal_jid = self.config.normalize_jid(jid);
local subs = self.subscriptions[normal_jid];
if subs then
if not subs[jid] then
subs[jid] = { [node] = true };
else
subs[jid][node] = true;
end
else
self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
end
self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
return true;
end
function service:remove_subscription(node, actor, jid)
-- Access checking
local cap;
if actor == true or jid == actor or self:jids_equal(actor, jid) then
cap = "unsubscribe";
else
cap = "unsubscribe_other";
end
if not self:may(node, actor, cap) then
return false, "forbidden";
end
if not self:may(node, jid, "be_unsubscribed") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
if not node_obj.subscribers[jid] then
return false, "not-subscribed";
end
node_obj.subscribers[jid] = nil;
local normal_jid = self.config.normalize_jid(jid);
local subs = self.subscriptions[normal_jid];
if subs then
local jid_subs = subs[jid];
if jid_subs then
jid_subs[node] = nil;
if next(jid_subs) == nil then
subs[jid] = nil;
end
end
if next(subs) == nil then
self.subscriptions[normal_jid] = nil;
end
end
self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
return true;
end
function service:remove_all_subscriptions(actor, jid)
local normal_jid = self.config.normalize_jid(jid);
local subs = self.subscriptions[normal_jid]
subs = subs and subs[jid];
if subs then
for node in pairs(subs) do
self:remove_subscription(node, true, jid);
end
end
return true;
end
function service:get_subscription(node, actor, jid)
-- Access checking
local cap;
if actor == true or jid == actor or self:jids_equal(actor, jid) then
cap = "get_subscription";
else
cap = "get_subscription_other";
end
if not self:may(node, actor, cap) then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
return true, node_obj.subscribers[jid];
end
function service:create(node, actor, options)
-- Access checking
if not self:may(node, actor, "create") then
return false, "forbidden";
end
--
if self.nodes[node] then
return false, "conflict";
end
self.nodes[node] = {
name = node;
subscribers = {};
config = setmetatable(options or {}, {__index=self.node_defaults});
affiliations = {};
};
self.data[node] = self.config.itemstore(self.nodes[node].config);
self.events.fire_event("node-created", { node = node, actor = actor });
local ok, err = self:set_affiliation(node, true, actor, "owner");
if not ok then
self.nodes[node] = nil;
self.data[node] = nil;
end
return ok, err;
end
function service:delete(node, actor)
-- Access checking
if not self:may(node, actor, "delete") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
self.nodes[node] = nil;
self.data[node] = nil;
self.events.fire_event("node-deleted", { node = node, actor = actor });
self.config.broadcaster("delete", node, node_obj.subscribers);
return true;
end
function service:publish(node, actor, id, item)
-- Access checking
if not self:may(node, actor, "publish") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
if not self.config.autocreate_on_publish then
return false, "item-not-found";
end
local ok, err = self:create(node, true);
if not ok then
return ok, err;
end
node_obj = self.nodes[node];
end
local node_data = self.data[node];
local ok = node_data:set(id, item);
if not ok then
return nil, "internal-server-error";
end
self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
self.config.broadcaster("items", node, node_obj.subscribers, item, actor);
return true;
end
function service:retract(node, actor, id, retract)
-- Access checking
if not self:may(node, actor, "retract") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if (not node_obj) or (not self.data[node]:get(id)) then
return false, "item-not-found";
end
local ok = self.data[node]:set(id, nil);
if not ok then
return nil, "internal-server-error";
end
self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
if retract then
self.config.broadcaster("items", node, node_obj.subscribers, retract);
end
return true
end
function service:purge(node, actor, notify)
-- Access checking
if not self:may(node, actor, "retract") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
self.data[node] = self.config.itemstore(self.nodes[node].config);
self.events.fire_event("node-purged", { node = node, actor = actor });
if notify then
self.config.broadcaster("purge", node, node_obj.subscribers);
end
return true
end
function service:get_items(node, actor, id)
-- Access checking
if not self:may(node, actor, "get_items") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
if id then -- Restrict results to a single specific item
return true, { id, [id] = self.data[node]:get(id) };
else
local data = {}
for key, value in self.data[node]:items() do
data[#data+1] = key;
data[key] = value;
end
return true, data;
end
end
function service:get_nodes(actor)
-- Access checking
if not self:may(nil, actor, "get_nodes") then
return false, "forbidden";
end
--
return true, self.nodes;
end
function service:get_subscriptions(node, actor, jid)
-- Access checking
local cap;
if actor == true or jid == actor or self:jids_equal(actor, jid) then
cap = "get_subscriptions";
else
cap = "get_subscriptions_other";
end
if not self:may(node, actor, cap) then
return false, "forbidden";
end
--
local node_obj;
if node then
node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
end
local normal_jid = self.config.normalize_jid(jid);
local subs = self.subscriptions[normal_jid];
-- We return the subscription object from the node to save
-- a get_subscription() call for each node.
local ret = {};
if subs then
for subscribed_jid, subscribed_nodes in pairs(subs) do
if node then -- Return only subscriptions to this node
if subscribed_nodes[node] then
ret[#ret+1] = {
node = node;
jid = subscribed_jid;
subscription = node_obj.subscribers[subscribed_jid];
};
end
else -- Return subscriptions to all nodes
local nodes = self.nodes;
for subscribed_node in pairs(subscribed_nodes) do
ret[#ret+1] = {
node = subscribed_node;
jid = subscribed_jid;
subscription = nodes[subscribed_node].subscribers[subscribed_jid];
};
end
end
end
end
return true, ret;
end
-- Access models only affect 'none' affiliation caps, service/default access level...
function service:set_node_capabilities(node, actor, capabilities)
-- Access checking
if not self:may(node, actor, "configure") then
return false, "forbidden";
end
--
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
node_obj.capabilities = capabilities;
return true;
end
function service:set_node_config(node, actor, new_config)
if not self:may(node, actor, "configure") then
return false, "forbidden";
end
local node_obj = self.nodes[node];
if not node_obj then
return false, "item-not-found";
end
for k,v in pairs(new_config) do
node_obj.config[k] = v;
end
local new_data = self.config.itemstore(self.nodes[node].config);
for key, value in self.data[node]:items() do
new_data:set(key, value);
end
self.data[node] = new_data;
return true;
end
return {
new = new;
};
prosody-0.10.0/util/iterators.lua 0000644 0001750 0001750 00000007525 13163172043 016700 0 ustar matthew matthew -- 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.
--
--[[ Iterators ]]--
local it = {};
local t_insert = table.insert;
local select, next = select, next;
local unpack = table.unpack or unpack; --luacheck: ignore 113
local pack = table.pack or function (...) return { n = select("#", ...), ... }; end
-- Reverse an iterator
function it.reverse(f, s, var)
local results = {};
-- First call the normal iterator
while true do
local ret = { f(s, var) };
var = ret[1];
if var == nil then break; end
t_insert(results, 1, ret);
end
-- Then return our reverse one
local i,max = 0, #results;
return function (_results)
if i= n then
return nil;
end
c = c + 1;
return f(_s, _var);
end, s, var;
end
-- Skip the first n items an iterator returns
function it.skip(n, f, s, var)
for _ = 1, n do
var = f(s, var);
end
return f, s, var;
end
-- Return the last n items an iterator returns
function it.tail(n, f, s, var)
local results, count = {}, 0;
while true do
local ret = pack(f(s, var));
var = ret[1];
if var == nil then break; end
results[(count%n)+1] = ret;
count = count + 1;
end
if n > count then n = count; end
local pos = 0;
return function ()
pos = pos + 1;
if pos > n then return nil; end
local ret = results[((count-1+pos)%n)+1];
return unpack(ret, 1, ret.n);
end
--return reverse(head(n, reverse(f, s, var))); -- !
end
function it.filter(filter, f, s, var)
if type(filter) ~= "function" then
local filter_value = filter;
function filter(x) return x ~= filter_value; end
end
return function (_s, _var)
local ret;
repeat ret = pack(f(_s, _var));
_var = ret[1];
until _var == nil or filter(unpack(ret, 1, ret.n));
return unpack(ret, 1, ret.n);
end, s, var;
end
local function _ripairs_iter(t, key) if key > 1 then return key-1, t[key-1]; end end
function it.ripairs(t)
return _ripairs_iter, t, #t+1;
end
local function _range_iter(max, curr) if curr < max then return curr + 1; end end
function it.range(x, y)
if not y then x, y = 1, x; end -- Default to 1..x if y not given
return _range_iter, y, x-1;
end
-- Convert the values returned by an iterator to an array
function it.to_array(f, s, var)
local t = {};
while true do
var = f(s, var);
if var == nil then break; end
t_insert(t, var);
end
return t;
end
-- Treat the return of an iterator as key,value pairs,
-- and build a table
function it.to_table(f, s, var)
local t, var2 = {};
while true do
var, var2 = f(s, var);
if var == nil then break; end
t[var] = var2;
end
return t;
end
return it;
prosody-0.10.0/util/http.lua 0000644 0001750 0001750 00000003370 13163172043 015635 0 ustar matthew matthew -- Prosody IM
-- Copyright (C) 2013 Florian Zeitz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local format, char = string.format, string.char;
local pairs, ipairs, tonumber = pairs, ipairs, tonumber;
local t_insert, t_concat = table.insert, table.concat;
local function urlencode(s)
return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end));
end
local function urldecode(s)
return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end));
end
local function _formencodepart(s)
return s and (s:gsub("%W", function (c)
if c ~= " " then
return format("%%%02x", c:byte());
else
return "+";
end
end));
end
local function formencode(form)
local result = {};
if form[1] then -- Array of ordered { name, value }
for _, field in ipairs(form) do
t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value));
end
else -- Unordered map of name -> value
for name, value in pairs(form) do
t_insert(result, _formencodepart(name).."=".._formencodepart(value));
end
end
return t_concat(result, "&");
end
local function formdecode(s)
if not s:match("=") then return urldecode(s); end
local r = {};
for k, v in s:gmatch("([^=&]*)=([^&]*)") do
k, v = k:gsub("%+", "%%20"), v:gsub("%+", "%%20");
k, v = urldecode(k), urldecode(v);
t_insert(r, { name = k, value = v });
r[k] = v;
end
return r;
end
local function contains_token(field, token)
field = ","..field:gsub("[ \t]", ""):lower()..",";
return field:find(","..token:lower()..",", 1, true) ~= nil;
end
return {
urlencode = urlencode, urldecode = urldecode;
formencode = formencode, formdecode = formdecode;
contains_token = contains_token;
};
prosody-0.10.0/util/hex.lua 0000644 0001750 0001750 00000000765 13163172043 015447 0 ustar matthew matthew local s_char = string.char;
local s_format = string.format;
local s_gsub = string.gsub;
local s_lower = string.lower;
local char_to_hex = {};
local hex_to_char = {};
do
local char, hex;
for i = 0,255 do
char, hex = s_char(i), s_format("%02x", i);
char_to_hex[char] = hex;
hex_to_char[hex] = char;
end
end
local function to(s)
return (s_gsub(s, ".", char_to_hex));
end
local function from(s)
return (s_gsub(s_lower(s), "%X*(%x%x)%X*", hex_to_char));
end
return { to = to, from = from }
prosody-0.10.0/util/hmac.lua 0000644 0001750 0001750 00000000606 13163172043 015565 0 ustar matthew matthew -- 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.
--
-- COMPAT: Only for external pre-0.9 modules
local hashes = require "util.hashes"
return { md5 = hashes.hmac_md5,
sha1 = hashes.hmac_sha1,
sha256 = hashes.hmac_sha256 };
prosody-0.10.0/util/template.lua 0000644 0001750 0001750 00000005470 13163172043 016474 0 ustar matthew matthew -- luacheck: ignore 213/i
local stanza_mt = require "util.stanza".stanza_mt;
local setmetatable = setmetatable;
local pairs = pairs;
local ipairs = ipairs;
local error = error;
local loadstring = loadstring;
local debug = debug;
local t_remove = table.remove;
local parse_xml = require "util.xml".parse;
local _ENV = nil;
local function trim_xml(stanza)
for i=#stanza,1,-1 do
local child = stanza[i];
if child.name then
trim_xml(child);
else
child = child:gsub("^%s*", ""):gsub("%s*$", "");
stanza[i] = child;
if child == "" then t_remove(stanza, i); end
end
end
end
local function create_string_string(str)
str = ("%q"):format(str);
str = str:gsub("{([^}]*)}", function(s)
return '"..(data["'..s..'"]or"").."';
end);
return str;
end
local function create_attr_string(attr, xmlns)
local str = '{';
for name,value in pairs(attr) do
if name ~= "xmlns" or value ~= xmlns then
str = str..("[%q]=%s;"):format(name, create_string_string(value));
end
end
return str..'}';
end
local function create_clone_string(stanza, lookup, xmlns)
if not lookup[stanza] then
local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns));
-- add tags
for i,tag in ipairs(stanza.tags) do
s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";";
end
s = s..'};';
-- add children
for i,child in ipairs(stanza) do
if child.name then
s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";";
else
s = s..create_string_string(child)..";"
end
end
s = s..'}, stanza_mt)';
s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings
local n = #lookup + 1;
lookup[n] = s;
lookup[stanza] = "_"..n;
end
return lookup[stanza];
end
local function create_cloner(stanza, chunkname)
local lookup = {};
local name = create_clone_string(stanza, lookup, "");
local src = "local setmetatable,stanza_mt=...;return function(data)";
for i=1,#lookup do
src = src.."local _"..i.."="..lookup[i]..";";
end
src = src.."return "..name..";end";
local f,err = loadstring(src, chunkname);
if not f then error(err); end
return f(setmetatable, stanza_mt);
end
local template_mt = { __tostring = function(t) return t.name end };
local function create_template(templates, text)
local stanza, err = parse_xml(text);
if not stanza then error(err); end
trim_xml(stanza);
local info = debug.getinfo(3, "Sl");
info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt);
templates[text] = template;
return template;
end
local templates = setmetatable({}, { __mode = 'k', __index = create_template });
return function(text)
return templates[text];
end;
prosody-0.10.0/util/sasl.lua 0000644 0001750 0001750 00000012011 13163172043 015610 0 ustar matthew matthew -- sasl.lua v0.4
-- Copyright (C) 2008-2010 Tobias Markmann
--
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local pairs, ipairs = pairs, ipairs;
local t_insert = table.insert;
local type = type
local setmetatable = setmetatable;
local assert = assert;
local require = require;
local _ENV = nil;
--[[
Authentication Backend Prototypes:
state = false : disabled
state = true : enabled
state = nil : non-existant
Channel Binding:
To enable support of channel binding in some mechanisms you need to provide appropriate callbacks in a table
at profile.cb.
Example:
profile.cb["tls-unique"] = function(self)
return self.user
end
]]
local method = {};
method.__index = method;
local mechanisms = {};
local backend_mechanism = {};
local mechanism_channelbindings = {};
-- register a new SASL mechanims
local function registerMechanism(name, backends, f, cb_backends)
assert(type(name) == "string", "Parameter name MUST be a string.");
assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
assert(type(f) == "function", "Parameter f MUST be a function.");
if cb_backends then assert(type(cb_backends) == "table"); end
mechanisms[name] = f
if cb_backends then
mechanism_channelbindings[name] = {};
for _, cb_name in ipairs(cb_backends) do
mechanism_channelbindings[name][cb_name] = true;
end
end
for _, backend_name in ipairs(backends) do
if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end
t_insert(backend_mechanism[backend_name], name);
end
end
-- create a new SASL object which can be used to authenticate clients
local function new(realm, profile)
local mechanisms = profile.mechanisms;
if not mechanisms then
mechanisms = {};
for backend, f in pairs(profile) do
if backend_mechanism[backend] then
for _, mechanism in ipairs(backend_mechanism[backend]) do
mechanisms[mechanism] = true;
end
end
end
profile.mechanisms = mechanisms;
end
return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method);
end
-- add a channel binding handler
function method:add_cb_handler(name, f)
if type(self.profile.cb) ~= "table" then
self.profile.cb = {};
end
self.profile.cb[name] = f;
return self;
end
-- get a fresh clone with the same realm and profile
function method:clean_clone()
return new(self.realm, self.profile)
end
-- get a list of possible SASL mechanims to use
function method:mechanisms()
local current_mechs = {};
for mech, _ in pairs(self.mechs) do
if mechanism_channelbindings[mech] then
if self.profile.cb then
local ok = false;
for cb_name, _ in pairs(self.profile.cb) do
if mechanism_channelbindings[mech][cb_name] then
ok = true;
end
end
if ok == true then current_mechs[mech] = true; end
end
else
current_mechs[mech] = true;
end
end
return current_mechs;
end
-- select a mechanism to use
function method:select(mechanism)
if not self.selected and self.mechs[mechanism] then
self.selected = mechanism;
return true;
end
end
-- feed new messages to process into the library
function method:process(message)
--if message == "" or message == nil then return "failure", "malformed-request" end
return mechanisms[self.selected](self, message);
end
-- load the mechanisms
require "util.sasl.plain" .init(registerMechanism);
require "util.sasl.digest-md5".init(registerMechanism);
require "util.sasl.anonymous" .init(registerMechanism);
require "util.sasl.scram" .init(registerMechanism);
require "util.sasl.external" .init(registerMechanism);
return {
registerMechanism = registerMechanism;
new = new;
};
prosody-0.10.0/COPYING 0000644 0001750 0001750 00000002113 13163172043 014223 0 ustar matthew matthew Copyright (c) 2008-2011 Matthew Wild
Copyright (c) 2008-2011 Waqas Hussain
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.
prosody-0.10.0/configure 0000755 0001750 0001750 00000035777 13163172043 015125 0 ustar matthew matthew #!/bin/sh
# Defaults
APP_NAME="Prosody"
APP_DIRNAME="prosody"
PREFIX="/usr/local"
SYSCONFDIR="$PREFIX/etc/$APP_DIRNAME"
LIBDIR="$PREFIX/lib"
DATADIR="$PREFIX/var/lib/$APP_DIRNAME"
LUA_SUFFIX=""
LUA_DIR="/usr"
LUA_BINDIR="/usr/bin"
LUA_INCDIR="/usr/include"
LUA_LIBDIR="/usr/lib"
IDN_LIB="idn"
ICU_FLAGS="-licui18n -licudata -licuuc"
OPENSSL_LIB="crypto"
CC="gcc"
LD="gcc"
RUNWITH="lua"
EXCERTS="yes"
PRNG=
PRNGLIBS=
CFLAGS="-fPIC -Wall -pedantic -std=c99"
LDFLAGS="-shared"
IDN_LIBRARY="idn"
# Help
show_help() {
cat </dev/null`
if [ -n "$prog" ]
then
dirname "$prog"
fi
}
die() {
echo "$*"
echo
echo "configure failed."
echo
exit 1
}
find_helper() {
explanation="$1"
shift
tried="$*"
while [ -n "$1" ]
do
found=`find_program "$1"`
if [ -n "$found" ]
then
echo "$1 found at $found"
HELPER=$1
return
fi
shift
done
echo "Could not find $explanation. Tried: $tried."
die "Make sure one of them is installed and available in your PATH."
}
case `echo -n x` in
-n*) echo_n_flag='';;
*) echo_n_flag='-n';;
esac
echo_n() {
echo $echo_n_flag "$*"
}
# ----------------------------------------------------------------------------
# MAIN PROGRAM
# ----------------------------------------------------------------------------
# Parse options
while [ -n "$1" ]
do
value="`echo $1 | sed 's/[^=]*.\(.*\)/\1/'`"
key="`echo $1 | sed 's/=.*//'`"
if `echo "$value" | grep "~" >/dev/null 2>/dev/null`
then
echo
echo '*WARNING*: the "~" sign is not expanded in flags.'
echo 'If you mean the home directory, use $HOME instead.'
echo
fi
case "$key" in
--help)
show_help
exit 0
;;
--prefix)
[ -n "$value" ] || die "Missing value in flag $key."
PREFIX="$value"
PREFIX_SET=yes
;;
--sysconfdir)
[ -n "$value" ] || die "Missing value in flag $key."
SYSCONFDIR="$value"
SYSCONFDIR_SET=yes
;;
--ostype)
# TODO make this a switch?
OSTYPE="$value"
OSTYPE_SET=yes
if [ "$OSTYPE" = "debian" ]; then
if [ "$LUA_SUFFIX_SET" != "yes" ]; then
LUA_SUFFIX="5.1";
LUA_SUFFIX_SET=yes
fi
if [ "$RUNWITH_SET" != "yes" ]; then
RUNWITH="lua$LUA_SUFFIX";
RUNWITH_SET=yes
fi
LUA_INCDIR="/usr/include/lua$LUA_SUFFIX"
LUA_INCDIR_SET=yes
CFLAGS="$CFLAGS -ggdb"
fi
if [ "$OSTYPE" = "macosx" ]; then
LUA_INCDIR=/usr/local/include;
LUA_INCDIR_SET=yes
LUA_LIBDIR=/usr/local/lib
LUA_LIBDIR_SET=yes
CFLAGS="$CFLAGS -mmacosx-version-min=10.3"
LDFLAGS="-bundle -undefined dynamic_lookup"
fi
if [ "$OSTYPE" = "linux" ]; then
LUA_INCDIR=/usr/local/include;
LUA_INCDIR_SET=yes
LUA_LIBDIR=/usr/local/lib
LUA_LIBDIR_SET=yes
CFLAGS="$CFLAGS -ggdb"
fi
if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]; then
LUA_INCDIR="/usr/local/include/lua51"
LUA_INCDIR_SET=yes
CFLAGS="-Wall -fPIC -I/usr/local/include"
LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared"
LUA_SUFFIX="51"
LUA_SUFFIX_SET=yes
LUA_DIR=/usr/local
LUA_DIR_SET=yes
CC=cc
LD=ld
fi
if [ "$OSTYPE" = "openbsd" ]; then
LUA_INCDIR="/usr/local/include";
LUA_INCDIR_SET="yes"
fi
if [ "$OSTYPE" = "netbsd" ]; then
LUA_INCDIR="/usr/pkg/include/lua-5.1"
LUA_INCDIR_SET=yes
LUA_LIBDIR="/usr/pkg/lib/lua/5.1"
LUA_LIBDIR_SET=yes
CFLAGS="-Wall -fPIC -I/usr/pkg/include"
LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared"
fi
if [ "$OSTYPE" = "pkg-config" ]; then
if [ "$LUA_SUFFIX_SET" != "yes" ]; then
LUA_SUFFIX="5.1";
LUA_SUFFIX_SET=yes
fi
LUA_CF="$(pkg-config --cflags-only-I lua$LUA_SUFFIX)"
LUA_CF="${LUA_CF#*-I}"
LUA_CF="${LUA_CF%% *}"
if [ "$LUA_CF" != "" ]; then
LUA_INCDIR="$LUA_CF"
LUA_INCDIR_SET=yes
fi
CFLAGS="$CFLAGS"
fi
;;
--libdir)
LIBDIR="$value"
LIBDIR_SET=yes
;;
--datadir)
DATADIR="$value"
DATADIR_SET=yes
;;
--lua-suffix)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_SUFFIX="$value"
LUA_SUFFIX_SET=yes
;;
--lua-version|--with-lua-version)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_VERSION="$value"
[ "$LUA_VERSION" = "5.1" -o "$LUA_VERSION" = "5.2" -o "$LUA_VERSION" = "5.3" ] || die "Invalid Lua version in flag $key."
LUA_VERSION_SET=yes
;;
--with-lua)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_DIR="$value"
LUA_DIR_SET=yes
;;
--with-lua-bin)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_BINDIR="$value"
LUA_BINDIR_SET=yes
;;
--with-lua-include)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_INCDIR="$value"
LUA_INCDIR_SET=yes
;;
--with-lua-lib)
[ -n "$value" ] || die "Missing value in flag $key."
LUA_LIBDIR="$value"
LUA_LIBDIR_SET=yes
;;
--with-idn)
IDN_LIB="$value"
;;
--idn-library)
IDN_LIBRARY="$value"
;;
--with-ssl)
OPENSSL_LIB="$value"
;;
--with-random)
case "$value" in
getrandom)
PRNG=GETRANDOM
;;
openssl)
PRNG=OPENSSL
;;
arc4random)
PRNG=ARC4RANDOM
;;
esac
;;
--cflags)
CFLAGS="$value"
;;
--add-cflags)
CFLAGS="$CFLAGS $value"
;;
--ldflags)
LDFLAGS="$value"
;;
--add-ldflags)
LDFLAGS="$LDFLAGS $value"
;;
--c-compiler)
CC="$value"
;;
--linker)
LD="$value"
;;
--runwith)
RUNWITH="$value"
RUNWITH_SET=yes
;;
--no-example-certs)
EXCERTS=
;;
--compiler-wrapper)
CC="$value $CC"
LD="$value $LD"
;;
*)
die "Error: Unknown flag: $1"
;;
esac
shift
done
if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
then SYSCONFDIR=/etc/$APP_DIRNAME
else SYSCONFDIR=$PREFIX/etc/$APP_DIRNAME
fi
fi
if [ "$PREFIX_SET" = "yes" -a ! "$DATADIR_SET" = "yes" ]
then
if [ "$PREFIX" = "/usr" ]
then DATADIR=/var/lib/$APP_DIRNAME
else DATADIR=$PREFIX/var/lib/$APP_DIRNAME
fi
fi
if [ "$PREFIX_SET" = "yes" -a ! "$LIBDIR_SET" = "yes" ]
then
LIBDIR=$PREFIX/lib
fi
detect_lua_version() {
detected_lua=`$1 -e 'print(_VERSION:match(" (5%.[123])$"))' 2> /dev/null`
if [ "$detected_lua" != "nil" ]
then
if [ "$LUA_VERSION_SET" != "yes" ]
then
echo "Lua version detected: $detected_lua"
LUA_VERSION=$detected_lua
return 0
elif [ "$LUA_VERSION" = "$detected_lua" ]
then
return 0
fi
fi
return 1
}
search_interpreter() {
suffix="$1"
if [ "$LUA_BINDIR_SET" = "yes" ]
then
find_lua="$LUA_BINDIR"
elif [ "$LUA_DIR_SET" = "yes" ]
then
LUA_BINDIR="$LUA_DIR/bin"
if [ -f "$LUA_BINDIR/lua$suffix" ]
then
find_lua="$LUA_BINDIR"
fi
else
find_lua=`find_program lua$suffix`
fi
if [ -n "$find_lua" -a -x "$find_lua/lua$suffix" ]
then
if detect_lua_version "$find_lua/lua$suffix"
then
echo "Lua interpreter found: $find_lua/lua$suffix..."
if [ "$LUA_BINDIR_SET" != "yes" ]
then
LUA_BINDIR="$find_lua"
fi
if [ "$LUA_DIR_SET" != "yes" ]
then
LUA_DIR=`dirname "$find_lua"`
fi
LUA_SUFFIX="$suffix"
return 0
fi
fi
return 1
}
lua_interp_found=no
if [ "$LUA_SUFFIX_SET" != "yes" ]
then
if [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.1" ]
then
suffixes="5.1 51 -5.1 -51"
elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.2" ]
then
suffixes="5.2 52 -5.2 -52"
elif [ "$LUA_VERSION_SET" = "yes" -a "$LUA_VERSION" = "5.3" ]
then
suffixes="5.3 53 -5.3 -53"
else
suffixes="5.1 51 -5.1 -51 5.2 52 -5.2 -52 5.3 53 -5.3 -53"
fi
for suffix in "" `echo $suffixes`
do
search_interpreter "$suffix" && {
lua_interp_found=yes
break
}
done
else
search_interpreter "$LUA_SUFFIX" && {
lua_interp_found=yes
}
fi
if [ "$lua_interp_found" != "yes" -a "$RUNWITH_SET" != "yes" ]
then
[ "$LUA_VERSION_SET" ] && { interp="Lua $LUA_VERSION" ;} || { interp="Lua" ;}
[ "$LUA_DIR_SET" -o "$LUA_BINDIR_SET" ] && { where="$LUA_BINDIR" ;} || { where="\$PATH" ;}
echo "$interp interpreter not found in $where"
die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help."
fi
if [ "$LUA_VERSION_SET" = "yes" -a "$RUNWITH_SET" != "yes" ]
then
echo_n "Checking if $LUA_BINDIR/lua$LUA_SUFFIX is Lua version $LUA_VERSION... "
if detect_lua_version "$LUA_BINDIR/lua$LUA_SUFFIX"
then
echo "yes"
else
echo "no"
die "You may want to use the flags --with-lua, --with-lua-bin and/or --lua-suffix. See --help."
fi
fi
if [ "$LUA_INCDIR_SET" != "yes" ]
then
LUA_INCDIR="$LUA_DIR/include"
fi
if [ "$LUA_LIBDIR_SET" != "yes" ]
then
LUA_LIBDIR="$LUA_DIR/lib"
fi
echo_n "Checking Lua includes... "
lua_h="$LUA_INCDIR/lua.h"
if [ -f "$lua_h" ]
then
echo "lua.h found in $lua_h"
else
v_dir="$LUA_INCDIR/lua/$LUA_VERSION"
lua_h="$v_dir/lua.h"
if [ -f "$lua_h" ]
then
echo "lua.h found in $lua_h"
LUA_INCDIR="$v_dir"
else
d_dir="$LUA_INCDIR/lua$LUA_VERSION"
lua_h="$d_dir/lua.h"
if [ -f "$lua_h" ]
then
echo "lua.h found in $lua_h (Debian/Ubuntu)"
LUA_INCDIR="$d_dir"
else
echo "lua.h not found (looked in $LUA_INCDIR, $v_dir, $d_dir)"
die "You may want to use the flag --with-lua or --with-lua-include. See --help."
fi
fi
fi
if [ "$lua_interp_found" = "yes" ]
then
echo_n "Checking if Lua header version matches that of the interpreter... "
header_version=$(sed -n 's/.*LUA_VERSION_NUM.*5.\(.\).*/5.\1/p' "$lua_h")
if [ "$header_version" = "$LUA_VERSION" ]
then
echo "yes"
else
echo "no"
echo "lua.h version mismatch (interpreter: $LUA_VERSION; lua.h: $header_version)."
die "You may want to use the flag --with-lua or --with-lua-include. See --help."
fi
fi
if [ "$IDN_LIBRARY" = "icu" ]
then
IDNA_LIBS="$ICU_FLAGS"
CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
fi
if [ "$IDN_LIBRARY" = "idn" ]
then
IDNA_LIBS="-l$IDN_LIB"
fi
if [ -f config.unix ]; then
rm -f config.unix
fi
if [ "$RUNWITH_SET" != yes ]; then
RUNWITH="lua$LUA_SUFFIX"
fi
OPENSSL_LIBS="-l$OPENSSL_LIB"
if [ "$PRNG" = "OPENSSL" ]; then
PRNGLIBS=$OPENSSL_LIBS
fi
# Write config
echo "Writing configuration..."
echo
rm -f built
cat < config.unix
# This file was automatically generated by the configure script.
# Run "./configure --help" for details.
LUA_VERSION=$LUA_VERSION
PREFIX=$PREFIX
SYSCONFDIR=$SYSCONFDIR
LIBDIR=$LIBDIR
DATADIR=$DATADIR
LUA_SUFFIX=$LUA_SUFFIX
LUA_DIR=$LUA_DIR
LUA_DIR_SET=$LUA_DIR_SET
LUA_INCDIR=$LUA_INCDIR
LUA_LIBDIR=$LUA_LIBDIR
LUA_BINDIR=$LUA_BINDIR
IDN_LIB=$IDN_LIB
IDNA_LIBS=$IDNA_LIBS
OPENSSL_LIBS=$OPENSSL_LIBS
CFLAGS=$CFLAGS
LDFLAGS=$LDFLAGS
CC=$CC
LD=$LD
RUNWITH=$RUNWITH
EXCERTS=$EXCERTS
RANDOM=$PRNG
RANDOM_LIBS=$PRNGLIBS
EOF
echo "Installation prefix: $PREFIX"
echo "$APP_NAME configuration directory: $SYSCONFDIR"
echo "Using Lua from: $LUA_DIR"
make clean > /dev/null 2> /dev/null
echo
echo "Done. You can now run 'make' to build."
echo
prosody-0.10.0/tools/ 0000775 0001750 0001750 00000000000 13163172043 014335 5 ustar matthew matthew prosody-0.10.0/tools/openfire2prosody.lua 0000644 0001750 0001750 00000006165 13163172043 020357 0 ustar matthew matthew #!/usr/bin/env lua
-- Prosody IM
-- Copyright (C) 2008-2009 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
package.path = package.path..";../?.lua";
package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
-- ugly workaround for getting datamanager to work outside of prosody :(
prosody = { };
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
elseif package.config:sub(1,1) == "/" then
prosody.platform = "posix";
end
local parse_xml = require "util.xml".parse;
-----------------------------------------------------------------------
package.loaded["util.logger"] = {init = function() return function() end; end}
local dm = require "util.datamanager"
dm.set_data_path("data");
local arg = ...;
local help = "/? -? ? /h -h /help -help --help";
if not arg or help:find(arg, 1, true) then
print([[Openfire importer for Prosody
Usage: openfire2prosody.lua filename.xml hostname
]]);
os.exit(1);
end
local host = select(2, ...) or "localhost";
local file = assert(io.open(arg));
local data = assert(file:read("*a"));
file:close();
local xml = assert(parse_xml(data));
assert(xml.name == "Openfire", "The input file is not an Openfire XML export");
local substatus_mapping = { ["0"] = "none", ["1"] = "to", ["2"] = "from", ["3"] = "both" };
for _,tag in ipairs(xml.tags) do
if tag.name == "User" then
local username, password, roster;
for _,tag in ipairs(tag.tags) do
if tag.name == "Username" then
username = tag:get_text();
elseif tag.name == "Password" then
password = tag:get_text();
elseif tag.name == "Roster" then
roster = {};
local pending = {};
for _,tag in ipairs(tag.tags) do
if tag.name == "Item" then
local jid = assert(tag.attr.jid, "Roster item has no JID");
if tag.attr.substatus ~= "-1" then
local item = {};
item.name = tag.attr.name;
item.subscription = assert(substatus_mapping[tag.attr.substatus], "invalid substatus");
item.ask = tag.attr.askstatus == "0" and "subscribe" or nil;
local groups = {};
for _,tag in ipairs(tag) do
if tag.name == "Group" then
groups[tag:get_text()] = true;
end
end
item.groups = groups;
roster[jid] = item;
end
if tag.attr.recvstatus == "1" then pending[jid] = true; end
end
end
if next(pending) then
roster[false] = { pending = pending };
end
end
end
assert(username and password, "No username or password");
local ret, err = dm.store(username, host, "accounts", {password = password});
print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
if roster then
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored roster: "..username.."@"..host.." = "..password);
end
end
end
prosody-0.10.0/tools/jabberd14sql2prosody.lua 0000644 0001750 0001750 00000075076 13163172043 021035 0 ustar matthew matthew #!/usr/bin/env lua
do
local _parse_sql_actions = { [0] =
0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 9, 2, 0, 10, 2, 0, 11, 2, 0, 13,
2, 1, 2, 2, 1, 6, 3, 0, 3, 4, 3, 0, 3, 5, 3, 0, 3, 7, 3, 0,
3, 8, 3, 0, 3, 12, 4, 0, 2, 3, 7, 4, 0, 3, 8, 11
};
local _parse_sql_trans_keys = { [0] =
0, 0, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 82, 82,
69, 69, 65, 65, 84, 84, 69, 69, 32, 32, 68, 84, 65,
65, 84, 84, 65, 65, 66, 66, 65, 65, 83, 83, 69, 69,
9, 47, 9, 96, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47,
10, 96, 10, 96, 9, 47, 9, 59, 45, 45, 10, 10, 42,
42, 10, 42, 10, 47, 65, 65, 66, 66, 76, 76, 69, 69,
32, 32, 73, 96, 70, 70, 32, 32, 78, 78, 79, 79, 84, 84,
32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83,
83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 40,
10, 10, 32, 41, 32, 32, 75, 96, 69, 69, 89, 89, 32, 32,
96, 96, 10, 96, 10, 96, 10, 10, 82, 82, 73, 73, 77,
77, 65, 65, 82, 82, 89, 89, 32, 32, 75, 75, 69, 69,
89, 89, 32, 32, 78, 78, 73, 73, 81, 81, 85, 85, 69, 69,
32, 32, 75, 75, 10, 96, 10, 96, 10, 10, 10, 59, 10,
59, 82, 82, 79, 79, 80, 80, 32, 32, 84, 84, 65, 65,
66, 66, 76, 76, 69, 69, 32, 32, 73, 73, 70, 70, 32, 32,
69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32,
32, 96, 96, 10, 96, 10, 96, 59, 59, 78, 78, 83, 83,
69, 69, 82, 82, 84, 84, 32, 32, 73, 73, 78, 78, 84, 84,
79, 79, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40,
86, 10, 41, 32, 32, 86, 86, 65, 65, 76, 76, 85, 85,
69, 69, 83, 83, 32, 32, 40, 40, 39, 78, 10, 92, 10, 92,
41, 44, 44, 59, 32, 78, 48, 57, 41, 57, 48, 57, 41,
57, 85, 85, 76, 76, 76, 76, 34, 116, 79, 79, 67, 67,
75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69,
83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 87,
87, 82, 82, 73, 73, 84, 84, 69, 69, 69, 69, 84, 84,
32, 32, 10, 59, 10, 59, 78, 83, 76, 76, 79, 79, 67, 67,
75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69,
69, 83, 83, 69, 69, 9, 85, 0
};
local _parse_sql_key_spans = { [0] =
0, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1,
39, 88, 1, 1, 1, 33, 38, 87, 87, 39, 51, 1, 1, 1, 33, 38, 1, 1, 1, 1,
1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1,
1, 10, 1, 22, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 50, 50, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 47, 32, 1, 1, 1, 1, 1,
1, 1, 1, 1, 40, 83, 83, 4, 16, 47, 10, 17, 10, 17, 1, 1, 1, 83, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1,
1, 50, 50, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 77
};
local _parse_sql_index_offsets = { [0] =
0, 0, 2, 4, 6, 40, 79, 81, 83, 85, 87, 89, 91, 109, 111, 113, 115, 117, 119, 121,
123, 163, 252, 254, 256, 258, 292, 331, 419, 507, 547, 599, 601, 603, 605, 639, 678, 680, 682, 684,
686, 688, 713, 715, 717, 719, 721, 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, 829, 917, 919,
921, 923, 934, 936, 959, 961, 963, 965, 967, 1055, 1143, 1145, 1147, 1149, 1151, 1153, 1155, 1157, 1159, 1161,
1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1269, 1357, 1359, 1410, 1461, 1463, 1465, 1467, 1469, 1471,
1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, 1497, 1499, 1501, 1503, 1591, 1679, 1681, 1683,
1685, 1687, 1689, 1691, 1693, 1695, 1697, 1699, 1701, 1703, 1705, 1793, 1881, 1883, 1931, 1964, 1966, 1968, 1970, 1972,
1974, 1976, 1978, 1980, 1982, 2023, 2107, 2191, 2196, 2213, 2261, 2272, 2290, 2301, 2319, 2321, 2323, 2325, 2409, 2411,
2413, 2415, 2417, 2419, 2421, 2423, 2425, 2427, 2429, 2431, 2433, 2521, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623,
2625, 2627, 2678, 2729, 2736, 2738, 2740, 2742, 2744, 2746, 2748, 2750, 2752, 2754, 2756, 2758, 2760
};
local _parse_sql_indicies = { [0] =
0, 1, 2, 0, 3, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3,
4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 6, 3, 7,
1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20,
1, 21, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
1, 25, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24,
1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 27, 1, 23, 27, 28, 1, 29, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 29, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 30, 28, 28, 28, 28, 22, 28, 32, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 1, 31, 32,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 33, 31, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 36, 1, 37, 1, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 36, 1, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 38,
1, 35, 38, 39, 1, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 40,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 39, 34, 39, 42, 1,
43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52,
1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 48,
1, 63, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
62, 62, 62, 62, 62, 62, 62, 1, 62, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, 64, 67, 1, 68,
1, 69, 1, 70, 1, 1, 1, 1, 1, 1, 1, 1, 71, 1, 72, 1, 73, 1, 1, 1,
1, 74, 1, 1, 1, 1, 75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77,
1, 78, 1, 79, 1, 80, 1, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 1, 81, 82, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
81, 83, 81, 69, 83, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91,
1, 92, 1, 93, 1, 83, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 73,
1, 101, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 1, 100, 103, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 104, 102, 105, 83, 106,
71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 108, 71, 71, 71, 71, 71, 71, 71, 71, 71,
71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107,
71, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118,
1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128,
1, 129, 1, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 1, 130, 131, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 132, 130, 6,
1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142,
1, 143, 1, 144, 1, 146, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145,
145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 1, 145, 148, 147, 147, 147, 147, 147, 147,
147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 149,
147, 150, 1, 151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 152, 1, 153, 151, 151, 151, 151, 151, 151, 151, 151,
151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151,
151, 151, 154, 151, 155, 1, 152, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1,
162, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 166, 1, 168, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 167, 167, 167, 167, 167, 167, 167,
167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167,
167, 167, 167, 167, 167, 170, 167, 172, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 171, 171, 171,
171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
171, 171, 171, 171, 171, 171, 171, 171, 171, 174, 171, 175, 1, 1, 176, 1, 161, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 177, 1, 178, 1, 1, 1, 1, 1, 1,
163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166,
1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 180, 1, 1, 181, 1, 182, 1, 179,
179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183,
1, 180, 1, 1, 181, 1, 1, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 184,
1, 185, 1, 186, 1, 171, 1, 1, 171, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 171,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 171, 1, 171, 1, 1, 171, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 171, 1, 1, 1, 171, 1, 171, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192,
1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 200, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 1,
199, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
199, 199, 199, 199, 199, 199, 199, 201, 199, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 132,
1, 207, 1, 208, 1, 209, 1, 210, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 2, 209,
209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
209, 209, 209, 209, 209, 209, 209, 211, 209, 212, 1, 1, 1, 1, 213, 1, 214, 1, 215, 1,
216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 132, 1, 127, 1,
6, 2, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 224, 1, 225, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 226, 227,
1, 1, 1, 1, 228, 1, 1, 229, 1, 1, 1, 1, 1, 1, 230, 1, 231, 1, 0
};
local _parse_sql_trans_targs = { [0] =
2, 0, 196, 4, 4, 5, 196, 7, 8, 9, 10, 11, 12, 13, 36, 14, 15, 16, 17, 18,
19, 20, 21, 21, 22, 24, 27, 23, 25, 25, 26, 28, 28, 29, 30, 30, 31, 33, 32, 34,
34, 35, 37, 38, 39, 40, 41, 42, 56, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 57, 57, 57, 57, 58, 59, 60, 61, 62, 92, 63, 64, 71, 82, 89, 65, 66, 67,
68, 69, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88,
90, 90, 90, 90, 91, 70, 92, 93, 196, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 116, 117, 119, 120, 121, 122, 123, 124, 125,
126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 133, 134, 137, 134, 135, 136, 138, 139, 140, 141,
142, 143, 144, 145, 150, 151, 154, 146, 146, 147, 157, 146, 146, 147, 157, 148, 149, 196, 144, 151,
148, 149, 152, 153, 155, 156, 147, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
171, 172, 173, 174, 175, 176, 177, 179, 180, 181, 181, 182, 184, 195, 185, 186, 187, 188, 189, 190,
191, 192, 193, 194, 1, 3, 6, 94, 118, 158, 178, 183
};
local _parse_sql_trans_actions = { [0] =
1, 0, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1,
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 5, 20, 1, 3, 30, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
5, 20, 1, 3, 26, 3, 3, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 5, 20, 1, 3, 42, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1,
1, 1, 11, 1, 5, 5, 1, 5, 20, 46, 5, 1, 3, 34, 1, 14, 1, 17, 1, 1,
51, 38, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
local parse_sql_start = 196;
local parse_sql_first_final = 196;
local parse_sql_error = 0;
local parse_sql_en_main = 196;
local _sql_unescapes = setmetatable({
["\\0"] = "\0";
["\\'"] = "'";
["\\\""] = "\"";
["\\b"] = "\b";
["\\n"] = "\n";
["\\r"] = "\r";
["\\t"] = "\t";
["\\Z"] = "\26";
["\\\\"] = "\\";
["\\%"] = "%";
["\\_"] = "_";
},{ __index = function(t, s) assert(false, "Unknown escape sequences: "..s); end });
function parse_sql(data, h)
local p = 1;
local pe = #data + 1;
local cs;
local pos_char, pos_line = 1, 1;
local mark, token;
local table_name, columns, value_lists, value_list, value_count;
cs = parse_sql_start;
-- ragel flat exec
local testEof = false;
local _slen = 0;
local _trans = 0;
local _keys = 0;
local _inds = 0;
local _acts = 0;
local _nacts = 0;
local _tempval = 0;
local _goto_level = 0;
local _resume = 10;
local _eof_trans = 15;
local _again = 20;
local _test_eof = 30;
local _out = 40;
while true do -- goto loop
local _continue = false;
repeat
local _trigger_goto = false;
if _goto_level <= 0 then
-- noEnd
if p == pe then
_goto_level = _test_eof;
_continue = true; break;
end
-- errState != 0
if cs == 0 then
_goto_level = _out;
_continue = true; break;
end
end -- _goto_level <= 0
if _goto_level <= _resume then
_keys = cs * 2; -- LOCATE_TRANS
_inds = _parse_sql_index_offsets[cs];
_slen = _parse_sql_key_spans[cs];
if _slen > 0 and
_parse_sql_trans_keys[_keys] <= data:byte(p) and
data:byte(p) <= _parse_sql_trans_keys[_keys + 1] then
_trans = _parse_sql_indicies[ _inds + data:byte(p) - _parse_sql_trans_keys[_keys] ];
else _trans =_parse_sql_indicies[ _inds + _slen ]; end
cs = _parse_sql_trans_targs[_trans];
if _parse_sql_trans_actions[_trans] ~= 0 then
_acts = _parse_sql_trans_actions[_trans];
_nacts = _parse_sql_actions[_acts];
_acts = _acts + 1;
while _nacts > 0 do
_nacts = _nacts - 1;
_acts = _acts + 1;
_tempval = _parse_sql_actions[_acts - 1];
-- start action switch
if _tempval == 0 then --4 FROM_STATE_ACTION_SWITCH
-- line 34 "sql.rl" -- end of line directive
pos_char = pos_char + 1; -- ACTION
elseif _tempval == 1 then --4 FROM_STATE_ACTION_SWITCH
-- line 35 "sql.rl" -- end of line directive
pos_line = pos_line + 1; pos_char = 1; -- ACTION
elseif _tempval == 2 then --4 FROM_STATE_ACTION_SWITCH
-- line 38 "sql.rl" -- end of line directive
mark = p; -- ACTION
elseif _tempval == 3 then --4 FROM_STATE_ACTION_SWITCH
-- line 39 "sql.rl" -- end of line directive
token = data:sub(mark, p-1); -- ACTION
elseif _tempval == 4 then --4 FROM_STATE_ACTION_SWITCH
-- line 52 "sql.rl" -- end of line directive
table.insert(columns, token); columns[#columns] = token; -- ACTION
elseif _tempval == 5 then --4 FROM_STATE_ACTION_SWITCH
-- line 58 "sql.rl" -- end of line directive
table_name,columns = token,{}; -- ACTION
elseif _tempval == 6 then --4 FROM_STATE_ACTION_SWITCH
-- line 59 "sql.rl" -- end of line directive
h.create(table_name, columns); -- ACTION
elseif _tempval == 7 then --4 FROM_STATE_ACTION_SWITCH
-- line 65 "sql.rl" -- end of line directive
value_count = value_count + 1; value_list[value_count] = token:gsub("\\.", _sql_unescapes);
-- ACTION
elseif _tempval == 8 then --4 FROM_STATE_ACTION_SWITCH
-- line 68 "sql.rl" -- end of line directive
value_count = value_count + 1; value_list[value_count] = tonumber(token); -- ACTION
elseif _tempval == 9 then --4 FROM_STATE_ACTION_SWITCH
-- line 69 "sql.rl" -- end of line directive
value_count = value_count + 1; -- ACTION
elseif _tempval == 10 then --4 FROM_STATE_ACTION_SWITCH
-- line 71 "sql.rl" -- end of line directive
value_list,value_count = {},0; -- ACTION
elseif _tempval == 11 then --4 FROM_STATE_ACTION_SWITCH
-- line 71 "sql.rl" -- end of line directive
table.insert(value_lists, value_list); -- ACTION
elseif _tempval == 12 then --4 FROM_STATE_ACTION_SWITCH
-- line 74 "sql.rl" -- end of line directive
table_name,value_lists = token,{}; -- ACTION
elseif _tempval == 13 then --4 FROM_STATE_ACTION_SWITCH
-- line 75 "sql.rl" -- end of line directive
h.insert(table_name, value_lists); -- ACTION
end
-- line 355 "sql.lua" -- end of line directive
-- end action switch
end -- while _nacts
end
if _trigger_goto then _continue = true; break; end
end -- endif
if _goto_level <= _again then
if cs == 0 then
_goto_level = _out;
_continue = true; break;
end
p = p + 1;
if p ~= pe then
_goto_level = _resume;
_continue = true; break;
end
end -- _goto_level <= _again
if _goto_level <= _test_eof then
end -- _goto_level <= _test_eof
if _goto_level <= _out then break; end
_continue = true;
until true;
if not _continue then break; end
end -- endif _goto_level <= out
-- end of execute block
if cs < parse_sql_first_final then
print("parse_sql: there was an error, line "..pos_line.." column "..pos_char);
else
print("Success. EOF at line "..pos_line.." column "..pos_char)
end
end
end
-- import modules
package.path = package.path..";../?.lua;";
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
-- ugly workaround for getting datamanager to work outside of prosody :(
prosody = { };
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
elseif package.config:sub(1,1) == "/" then
prosody.platform = "_posix";
end
package.loaded["util.logger"] = {init = function() return function() end; end}
local dm = require "util.datamanager";
dm.set_data_path("data");
local datetime = require "util.datetime";
local st = require "util.stanza";
local parse_xml = require "util.xml".parse;
function store_password(username, host, password)
-- create or update account for username@host
local ret, err = dm.store(username, host, "accounts", {password = password});
print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
end
function store_vcard(username, host, stanza)
-- create or update vCard for username@host
local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza));
print("["..(err or "success").."] stored vCard: "..username.."@"..host);
end
function store_roster(username, host, roster_items)
-- fetch current roster-table for username@host if he already has one
local roster = dm.load(username, host, "roster") or {};
-- merge imported roster-items with loaded roster
for item_tag in roster_items:childtags() do
-- jid for this roster-item
local item_jid = item_tag.attr.jid
-- validate item stanzas
if (item_tag.name == "item") and (item_jid ~= "") then
-- prepare roster item
-- TODO: is the subscription attribute optional?
local item = {subscription = item_tag.attr.subscription, groups = {}};
-- optional: give roster item a real name
if item_tag.attr.name then
item.name = item_tag.attr.name;
end
-- optional: iterate over group stanzas inside item stanza
for group_tag in item_tag:childtags() do
local group_name = group_tag:get_text();
if (group_tag.name == "group") and (group_name ~= "") then
item.groups[group_name] = true;
else
print("[error] invalid group stanza: "..group_tag:pretty_print());
end
end
-- store item in roster
roster[item_jid] = item;
print("[success] roster entry: " ..username.."@"..host.." - "..item_jid);
else
print("[error] invalid roster stanza: " ..item_tag:pretty_print());
end
end
-- store merged roster-table
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored roster: " ..username.."@"..host);
end
function store_subscription_request(username, host, presence_stanza)
local from_bare = presence_stanza.attr.from;
-- fetch current roster-table for username@host if he already has one
local roster = dm.load(username, host, "roster") or {};
local item = roster[from_bare];
if item and (item.subscription == "from" or item.subscription == "both") then
return; -- already subscribed, do nothing
end
-- add to table of pending subscriptions
if not roster.pending then roster.pending = {}; end
roster.pending[from_bare] = true;
-- store updated roster-table
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare);
end
local os_date = os.date;
local os_time = os.time;
local os_difftime = os.difftime;
function datetime_parse(s)
if s then
local year, month, day, hour, min, sec, tzd;
year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$");
if year then
local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone
local tzd_offset = 0;
if tzd ~= "" and tzd ~= "Z" then
local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)");
if not sign then return; end
if #m ~= 2 then m = "0"; end
h, m = tonumber(h), tonumber(m);
tzd_offset = h * 60 * 60 + m * 60;
if sign == "-" then tzd_offset = -tzd_offset; end
end
sec = (sec + time_offset) - tzd_offset;
return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false});
end
end
end
function store_offline_messages(username, host, stanza)
-- TODO: maybe use list_load(), append and list_store() instead
-- of constantly reopening the file with list_append()?
--for ch in offline_messages:childtags() do
--print("message :"..ch:pretty_print());
stanza.attr.node = nil;
local stamp = stanza:get_child("x", "jabber:x:delay");
if not stamp or not stamp.attr.stamp then print(2) return; end
for i=1,#stanza do if stanza[i] == stamp then table.remove(stanza, i); break; end end
for i=1,#stanza.tags do if stanza.tags[i] == stamp then table.remove(stanza.tags, i); break; end end
local parsed_stamp = datetime_parse(stamp.attr.stamp);
if not parsed_stamp then print(1, stamp.attr.stamp) return; end
stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(parsed_stamp), datetime.legacy(parsed_stamp);
local ret, err = dm.list_append(username, host, "offline", st.preserialize(stanza));
print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..stanza.attr.from);
--end
end
-- load data
local arg = ...;
local help = "/? -? ? /h -h /help -help --help";
if not arg or help:find(arg, 1, true) then
print([[XEP-227 importer for Prosody
Usage: jabberd14sql2prosody.lua filename.sql
]]);
os.exit(1);
end
local f = io.open(arg);
local s = f:read("*a");
f:close();
local table_count = 0;
local insert_count = 0;
local row_count = 0;
-- parse
parse_sql(s, {
create = function(table_name, columns)
--[[print(table_name);]]
table_count = table_count + 1;
end;
insert = function(table_name, value_lists)
--[[print(table_name, #value_lists);]]
insert_count = insert_count + 1;
row_count = row_count + #value_lists;
for _,value_list in ipairs(value_lists) do
if table_name == "users" then
local user, realm, password = unpack(value_list);
store_password(user, realm, password);
elseif table_name == "roster" then
local user, realm, xml = unpack(value_list);
local stanza,err = parse_xml(xml);
if stanza then
store_roster(user, realm, stanza);
else
print("[error] roster: XML parsing failed for "..user.."@"..realm..": "..err);
end
elseif table_name == "vcard" then
local user, realm, name, email, nickname, birthday, photo, xml = unpack(value_list);
if xml then
local stanza,err = parse_xml(xml);
if stanza then
store_vcard(user, realm, stanza);
else
print("[error] vcard: XML parsing failed for "..user.."@"..realm..": "..err);
end
else
--print("[warn] vcard: NULL vCard for "..user.."@"..realm..": "..err);
end
elseif table_name == "storedsubscriptionrequests" then
local user, realm, fromjid, xml = unpack(value_list);
local stanza,err = parse_xml(xml);
if stanza then
store_subscription_request(user, realm, stanza);
else
print("[error] storedsubscriptionrequests: XML parsing failed for "..user.."@"..realm..": "..err);
end
elseif table_name == "messages" then
--local user, realm, node, correspondent, type, storetime, delivertime, subject, body, xml = unpack(value_list);
local user, realm, type, xml = value_list[1], value_list[2], value_list[5], value_list[10];
if type == "offline" and xml ~= "" then
local stanza,err = parse_xml(xml);
if stanza then
store_offline_messages(user, realm, stanza);
else
print("[error] offline messages: XML parsing failed for "..user.."@"..realm..": "..err);
print(unpack(value_list));
end
end
end
end
end;
});
print("table_count", table_count);
print("insert_count", insert_count);
print("row_count", row_count);
prosody-0.10.0/tools/ejabberd2prosody.lua 0000755 0001750 0001750 00000027424 13163172043 020312 0 ustar matthew matthew #!/usr/bin/env lua
-- 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.
--
package.path = package.path ..";../?.lua";
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
local erlparse = require "erlparse";
prosody = {};
package.loaded["util.logger"] = {init = function() return function() end; end}
local serialize = require "util.serialization".serialize;
local st = require "util.stanza";
local dm = require "util.datamanager"
dm.set_data_path("data");
function build_stanza(tuple, stanza)
assert(type(tuple) == "table", "XML node is of unexpected type: "..type(tuple));
if tuple[1] == "xmlelement" or tuple[1] == "xmlel" then
assert(type(tuple[2]) == "string", "element name has type: "..type(tuple[2]));
assert(type(tuple[3]) == "table", "element attribute array has type: "..type(tuple[3]));
assert(type(tuple[4]) == "table", "element children array has type: "..type(tuple[4]));
local name = tuple[2];
local attr = {};
for _, a in ipairs(tuple[3]) do
if type(a[1]) == "string" and type(a[2]) == "string" then attr[a[1]] = a[2]; end
end
local up;
if stanza then stanza:tag(name, attr); up = true; else stanza = st.stanza(name, attr); end
for _, a in ipairs(tuple[4]) do build_stanza(a, stanza); end
if up then stanza:up(); else return stanza end
elseif tuple[1] == "xmlcdata" then
if type(tuple[2]) ~= "table" then
assert(type(tuple[2]) == "string", "XML CDATA has unexpected type: "..type(tuple[2]));
stanza:text(tuple[2]);
end -- else it's [], i.e., the null value, used for the empty string
else
error("unknown element type: "..serialize(tuple));
end
end
function build_time(tuple)
local Megaseconds,Seconds,Microseconds = unpack(tuple);
return Megaseconds * 1000000 + Seconds;
end
function build_jid(tuple, full)
local node, jid, resource = tuple[1], tuple[2], tuple[3]
if type(node) == "string" and node ~= "" then
jid = tuple[1] .. "@" .. jid;
end
if full and type(resource) == "string" and resource ~= "" then
jid = jid .. "/" .. resource;
end
return jid;
end
function vcard(node, host, stanza)
local ret, err = dm.store(node, host, "vcard", st.preserialize(stanza));
print("["..(err or "success").."] vCard: "..node.."@"..host);
end
function password(node, host, password)
local data = {};
if type(password) == "string" then
data.password = password;
elseif type(password) == "table" and password[1] == "scram" then
local unb64 = require"mime".unb64;
local function hex(s)
return s:gsub(".", function (c)
return ("%02x"):format(c:byte());
end);
end
data.stored_key = hex(unb64(password[2]));
data.server_key = hex(unb64(password[3]));
data.salt = unb64(password[4]);
data.iteration_count = password[5];
end
local ret, err = dm.store(node, host, "accounts", data);
print("["..(err or "success").."] accounts: "..node.."@"..host);
end
function roster(node, host, jid, item)
local roster = dm.load(node, host, "roster") or {};
roster[jid] = item;
local ret, err = dm.store(node, host, "roster", roster);
print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid);
end
function roster_pending(node, host, jid)
local roster = dm.load(node, host, "roster") or {};
roster.pending = roster.pending or {};
roster.pending[jid] = true;
local ret, err = dm.store(node, host, "roster", roster);
print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid);
end
function private_storage(node, host, xmlns, stanza)
local private = dm.load(node, host, "private") or {};
private[stanza.name..":"..xmlns] = st.preserialize(stanza);
local ret, err = dm.store(node, host, "private", private);
print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns);
end
function offline_msg(node, host, t, stanza)
stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t);
stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t);
local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza));
print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t));
end
function privacy(node, host, default, lists)
local privacy = { lists = {} };
local count = 0;
if default then privacy.default = default; end
for _, inlist in ipairs(lists) do
local name, items = inlist[1], inlist[2];
local list = { name = name; items = {}; };
local orders = {};
for _, item in pairs(items) do
repeat
if item[1] ~= "listitem" then print("[error] privacy: unhandled item: "..tostring(item[1])); break; end
local _type, value = item[2], item[3];
if _type == "jid" then
if type(value) ~= "table" then print("[error] privacy: jid value is not valid: "..tostring(value)); break; end
local _node, _host, _resource = value[1], value[2], value[3];
value = build_jid(value, true)
elseif _type == "none" then
_type = nil;
value = nil;
elseif _type == "group" then
if type(value) ~= "string" then print("[error] privacy: group value is not string: "..tostring(value)); break; end
elseif _type == "subscription" then
if value~="both" and value~="from" and value~="to" and value~="none" then
print("[error] privacy: subscription value is invalid: "..tostring(value)); break;
end
else print("[error] privacy: invalid item type: "..tostring(_type)); break; end
local action = item[4];
if action ~= "allow" and action ~= "deny" then print("[error] privacy: unhandled action: "..tostring(action)); break; end
local order = item[5];
if type(order) ~= "number" or order<0 then print("[error] privacy: order is not numeric: "..tostring(order)); break; end
if orders[order] then print("[error] privacy: duplicate order value: "..tostring(order)); break; end
orders[order] = true;
local match_all = item[6];
local match_iq = item[7];
local match_message = item[8];
local match_presence_in = item[9];
local match_presence_out = item[10];
list.items[#list.items+1] = {
type = _type;
value = value;
action = action;
order = order;
message = match_message == "true";
iq = match_iq == "true";
["presence-in"] = match_presence_in == "true";
["presence-out"] = match_presence_out == "true";
};
until true;
end
table.sort(list.items, function(a, b) return a.order < b.order; end);
if privacy.lists[list.name] then print("[warn] duplicate privacy list: "..tostring(list.name)); end
privacy.lists[list.name] = list;
count = count + 1;
end
if default and not privacy.lists[default] then
if default == "none" then privacy.default = nil;
else print("[warn] default privacy list doesn't exist: "..tostring(default)); end
end
local ret, err = dm.store(node, host, "privacy", privacy);
print("["..(err or "success").."] privacy: " ..node.."@"..host.." - "..count.." list(s)");
end
function muc_room(node, host, properties)
local store = { jid = node.."@"..host, _data = {}, _affiliations = {} };
for _,aff in ipairs(properties.affiliations) do
store._affiliations[build_jid(aff[1])] = aff[2][1] or aff[2];
end
store._data.subject = properties.subject;
if properties.subject_author then
store._data.subject_from = store.jid .. "/" .. properties.subject_author;
end
store._data.name = properties.title;
store._data.description = properties.description;
store._data.password = properties.password;
store._data.moderated = (properties.moderated == "true") or nil;
store._data.members_only = (properties.members_only == "true") or nil;
store._data.persistent = (properties.persistent == "true") or nil;
store._data.changesubject = (properties.allow_change_subj == "true") or nil;
store._data.whois = properties.anonymous == "true" and "moderators" or "anyone";
store._data.hidden = (properties.public_list == "false") or nil;
if not store._data.persistent then
return print("[error] muc_room: skipping non-persistent room: "..node.."@"..host);
end
local ret, err = dm.store(node, host, "config", store);
if ret then
ret, err = dm.load(nil, host, "persistent");
if ret or not err then
ret = ret or {};
ret[store.jid] = true;
ret, err = dm.store(nil, host, "persistent", ret);
end
end
print("["..(err or "success").."] muc_room: " ..node.."@"..host);
end
local filters = {
passwd = function(tuple)
password(tuple[2][1], tuple[2][2], tuple[3]);
end;
vcard = function(tuple)
vcard(tuple[2][1], tuple[2][2], build_stanza(tuple[3]));
end;
roster = function(tuple)
local node = tuple[3][1]; local host = tuple[3][2];
local contact = build_jid(tuple[4]);
local name = tuple[5]; local subscription = tuple[6];
local ask = tuple[7]; local groups = tuple[8];
if type(name) ~= type("") then name = nil; end
if ask == "none" then
ask = nil;
elseif ask == "out" then
ask = "subscribe"
elseif ask == "in" then
roster_pending(node, host, contact);
ask = nil;
elseif ask == "both" then
roster_pending(node, host, contact);
ask = "subscribe";
else error("Unknown ask type: "..ask); end
if subscription ~= "both" and subscription ~= "from" and subscription ~= "to" and subscription ~= "none" then error(subscription) end
local item = {name = name, ask = ask, subscription = subscription, groups = {}};
for _, g in ipairs(groups) do
if type(g) == "string" then
item.groups[g] = true;
end
end
roster(node, host, contact, item);
end;
private_storage = function(tuple)
private_storage(tuple[2][1], tuple[2][2], tuple[2][3], build_stanza(tuple[3]));
end;
offline_msg = function(tuple)
offline_msg(tuple[2][1], tuple[2][2], build_time(tuple[3]), build_stanza(tuple[7]));
end;
privacy = function(tuple)
privacy(tuple[2][1], tuple[2][2], tuple[3], tuple[4]);
end;
muc_room = function(tuple)
local properties = {};
for _,pair in ipairs(tuple[3]) do
if not(type(pair[2]) == "table" and #pair[2] == 0) then -- skip nil values
properties[pair[1]] = pair[2];
end
end
muc_room(tuple[2][1], tuple[2][2], properties);
end;
--[=[config = function(tuple)
if tuple[2] == "hosts" then
local output = io.output(); io.output("prosody.cfg.lua");
io.write("-- Configuration imported from ejabberd --\n");
io.write([[Host "*"
modules_enabled = {
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
"roster"; -- Allow users to have a roster. Recommended ;)
"register"; -- Allow users to register on this server using a client
"tls"; -- Add support for secure TLS on c2s/s2s connections
"vcard"; -- Allow users to set vCards
"private"; -- Private XML storage (for room bookmarks, etc.)
"version"; -- Replies to server version requests
"dialback"; -- s2s dialback support
"uptime";
"disco";
"time";
"ping";
--"selftests";
};
]]);
for _, h in ipairs(tuple[3]) do
io.write("Host \"" .. h .. "\"\n");
end
io.output(output);
print("prosody.cfg.lua created");
end
end;]=]
};
local arg = ...;
local help = "/? -? ? /h -h /help -help --help";
if not arg or help:find(arg, 1, true) then
print([[ejabberd db dump importer for Prosody
Usage: ]]..my_name..[[ filename.txt
The file can be generated from ejabberd using:
sudo ejabberdctl dump filename.txt
Note: The path of ejabberdctl depends on your ejabberd installation, and ejabberd needs to be running for ejabberdctl to work.]]);
os.exit(1);
end
local count = 0;
local t = {};
for item in erlparse.parseFile(arg) do
count = count + 1;
local name = item[1];
t[name] = (t[name] or 0) + 1;
--print(count, serialize(item));
if filters[name] then filters[name](item); end
end
--print(serialize(t));
prosody-0.10.0/tools/erlparse.lua 0000644 0001750 0001750 00000010647 13163172043 016663 0 ustar matthew matthew -- 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 string_byte, string_char = string.byte, string.char;
local t_concat, t_insert = table.concat, table.insert;
local type, tonumber, tostring = type, tonumber, tostring;
local file = nil;
local last = nil;
local line = 1;
local function read(expected)
local ch;
if last then
ch = last; last = nil;
else
ch = file:read(1);
if ch == "\n" then line = line + 1; end
end
if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end
return ch;
end
local function pushback(ch)
if last then error(); end
last = ch;
end
local function peek()
if not last then last = read(); end
return last;
end
local _A, _a, _Z, _z, _0, _9, __, _at, _space, _minus = string_byte("AaZz09@_ -", 1, 10);
local function isLowerAlpha(ch)
ch = string_byte(ch) or 0;
return (ch >= _a and ch <= _z);
end
local function isNumeric(ch)
ch = string_byte(ch) or 0;
return (ch >= _0 and ch <= _9) or ch == _minus;
end
local function isAtom(ch)
ch = string_byte(ch) or 0;
return (ch >= _A and ch <= _Z) or (ch >= _a and ch <= _z) or (ch >= _0 and ch <= _9) or ch == __ or ch == _at;
end
local function isSpace(ch)
ch = string_byte(ch) or "x";
return ch <= _space;
end
local escapes = {["\\b"]="\b", ["\\d"]="\127", ["\\e"]="\27", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]=" ", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"};
local function readString()
read("\""); -- skip quote
local slash = nil;
local str = {};
while true do
local ch = read();
if slash then
slash = slash..ch;
if not escapes[slash] then error("Unknown escape sequence: "..slash); end
str[#str+1] = escapes[slash];
slash = nil;
elseif ch == "\"" then
break;
elseif ch == "\\" then
slash = ch;
else
str[#str+1] = ch;
end
end
return t_concat(str);
end
local function readAtom1()
local var = { read() };
while isAtom(peek()) do
var[#var+1] = read();
end
return t_concat(var);
end
local function readAtom2()
local str = { read("'") };
local slash = nil;
while true do
local ch = read();
str[#str+1] = ch;
if ch == "'" and not slash then break; end
end
return t_concat(str);
end
local function readNumber()
local num = { read() };
while isNumeric(peek()) do
num[#num+1] = read();
end
if peek() == "." then
num[#num+1] = read();
while isNumeric(peek()) do
num[#num+1] = read();
end
end
return tonumber(t_concat(num));
end
local readItem = nil;
local function readTuple()
local t = {};
local s = {}; -- string representation
read(); -- read {, or [, or <
while true do
local item = readItem();
if not item then break; end
if type(item) ~= "number" or item > 255 then
s = nil;
elseif s then
s[#s+1] = string_char(item);
end
t_insert(t, item);
end
read(); -- read }, or ], or >
if s and #s > 0 then
return t_concat(s)
else
return t
end;
end
local function readBinary()
read("<"); -- read <
-- Discard PIDs
if isNumeric(peek()) then
while peek() ~= ">" do read(); end
read(">");
return {};
end
local t = readTuple();
read(">") -- read >
local ch = peek();
if type(t) == "string" then
-- binary is a list of integers
return t;
elseif type(t) == "table" then
if t[1] then
-- binary contains string
return t[1];
else
-- binary is empty
return "";
end;
else
error();
end
end
readItem = function()
local ch = peek();
if ch == nil then return nil end
if ch == "{" or ch == "[" then
return readTuple();
elseif isLowerAlpha(ch) then
return readAtom1();
elseif ch == "'" then
return readAtom2();
elseif isNumeric(ch) then
return readNumber();
elseif ch == "\"" then
return readString();
elseif ch == "<" then
return readBinary();
elseif isSpace(ch) or ch == "," or ch == "|" then
read();
return readItem();
else
--print("Unknown char: "..ch);
return nil;
end
end
local function readChunk()
local x = readItem();
if x then read("."); end
return x;
end
local function readFile(filename)
file = io.open(filename);
if not file then error("File not found: "..filename); os.exit(0); end
return function()
local x = readChunk();
if not x and peek() then error("Invalid char: "..peek()); end
return x;
end;
end
local _M = {};
function _M.parseFile(file)
return readFile(file);
end
return _M;
prosody-0.10.0/tools/ejabberdsql2prosody.lua 0000644 0001750 0001750 00000023113 13163172043 021016 0 ustar matthew matthew #!/usr/bin/env lua
-- 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.
--
prosody = {};
package.path = package.path ..";../?.lua";
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
local serialize = require "util.serialization".serialize;
local st = require "util.stanza";
local parse_xml = require "util.xml".parse;
package.loaded["util.logger"] = {init = function() return function() end; end}
local dm = require "util.datamanager"
dm.set_data_path("data");
function parseFile(filename)
------
local file = nil;
local last = nil;
local line = 1;
local function read(expected)
local ch;
if last then
ch = last; last = nil;
else
ch = file:read(1);
if ch == "\n" then line = line + 1; end
end
if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end
return ch;
end
local function peek()
if not last then last = read(); end
return last;
end
local escapes = {
["\\0"] = "\0";
["\\'"] = "'";
["\\\""] = "\"";
["\\b"] = "\b";
["\\n"] = "\n";
["\\r"] = "\r";
["\\t"] = "\t";
["\\Z"] = "\26";
["\\\\"] = "\\";
["\\%"] = "%";
["\\_"] = "_";
}
local function unescape(s)
return escapes[s] or error("Unknown escape sequence: "..s);
end
local function readString()
read("'");
local s = "";
while true do
local ch = peek();
if ch == "\\" then
s = s..unescape(read()..read());
elseif ch == "'" then
break;
else
s = s..read();
end
end
read("'");
return s;
end
local function readNonString()
local s = "";
while true do
if peek() == "," or peek() == ")" then
break;
else
s = s..read();
end
end
return tonumber(s);
end
local function readItem()
if peek() == "'" then
return readString();
else
return readNonString();
end
end
local function readTuple()
local items = {}
read("(");
while peek() ~= ")" do
table.insert(items, readItem());
if peek() == ")" then break; end
read(",");
end
read(")");
return items;
end
local function readTuples()
if peek() ~= "(" then read("("); end
local tuples = {};
while true do
table.insert(tuples, readTuple());
if peek() == "," then read() end
if peek() == ";" then break; end
end
return tuples;
end
local function readTableName()
local tname = "";
while peek() ~= "`" do tname = tname..read(); end
return tname;
end
local function readInsert()
if peek() == nil then return nil; end
for ch in ("INSERT INTO `"):gmatch(".") do -- find line starting with this
if peek() == ch then
read(); -- found
else -- match failed, skip line
while peek() and read() ~= "\n" do end
return nil;
end
end
local tname = readTableName();
read("`"); read(" ") -- expect this
if peek() == "(" then -- skip column list
repeat until read() == ")";
read(" ");
end
for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this
local tuples = readTuples();
read(";"); read("\n");
return tname, tuples;
end
local function readFile(filename)
file = io.open(filename);
if not file then error("File not found: "..filename); os.exit(0); end
local t = {};
while true do
local tname, tuples = readInsert();
if tname then
if t[tname] then
local t_name = t[tname];
for i=1,#tuples do
table.insert(t_name, tuples[i]);
end
else
t[tname] = tuples;
end
elseif peek() == nil then
break;
end
end
return t;
end
return readFile(filename);
------
end
local arg, hostname = ...;
local help = "/? -? ? /h -h /help -help --help";
if not(arg and hostname) or help:find(arg, 1, true) then
print([[ejabberd SQL DB dump importer for Prosody
Usage: ejabberdsql2prosody.lua filename.txt hostname
The file can be generated using mysqldump:
mysqldump db_name > filename.txt]]);
os.exit(1);
end
local map = {
["last"] = {"username", "seconds", "state"};
["privacy_default_list"] = {"username", "name"};
["privacy_list"] = {"username", "name", "id"};
["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"};
["private_storage"] = {"username", "namespace", "data"};
["rostergroups"] = {"username", "jid", "grp"};
["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"};
["spool"] = {"username", "xml", "seq"};
["users"] = {"username", "password"};
["vcard"] = {"username", "vcard"};
--["vcard_search"] = {};
}
local NULL = {};
local parsed = parseFile(arg);
for name, data in pairs(parsed) do
local m = map[name];
if m then
if #data > 0 and #data[1] ~= #m then
print("[warning] expected "..#m.." columns for table `"..name.."`, found "..#data[1]);
end
for i=1,#data do
local row = data[i];
for j=1,#m do
row[m[j]] = row[j];
row[j] = nil;
end
end
end
end
--print(serialize(t));
for _, row in ipairs(parsed["users"] or NULL) do
local node, password = row.username, row.password;
local ret, err = dm.store(node, hostname, "accounts", {password = password});
print("["..(err or "success").."] accounts: "..node.."@"..hostname);
end
function roster(node, host, jid, item)
local roster = dm.load(node, host, "roster") or {};
roster[jid] = item;
local ret, err = dm.store(node, host, "roster", roster);
print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid);
end
function roster_pending(node, host, jid)
local roster = dm.load(node, host, "roster") or {};
roster.pending = roster.pending or {};
roster.pending[jid] = true;
local ret, err = dm.store(node, host, "roster", roster);
print("["..(err or "success").."] roster-pending: " ..node.."@"..host.." - "..jid);
end
function roster_group(node, host, jid, group)
local roster = dm.load(node, host, "roster") or {};
local item = roster[jid];
if not item then print("Warning: No roster item "..jid.." for user "..node..", can't put in group "..group); return; end
item.groups[group] = true;
local ret, err = dm.store(node, host, "roster", roster);
print("["..(err or "success").."] roster-group: " ..node.."@"..host.." - "..jid.." - "..group);
end
function private_storage(node, host, xmlns, stanza)
local private = dm.load(node, host, "private") or {};
private[stanza.name..":"..xmlns] = st.preserialize(stanza);
local ret, err = dm.store(node, host, "private", private);
print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns);
end
function offline_msg(node, host, t, stanza)
stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t);
stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t);
local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza));
print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t));
end
for _, row in ipairs(parsed["rosterusers"] or NULL) do
local node, contact = row.username, row.jid;
local name = row.nick;
if name == "" then name = nil; end
local subscription = row.subscription;
if subscription == "N" then
subscription = "none"
elseif subscription == "B" then
subscription = "both"
elseif subscription == "F" then
subscription = "from"
elseif subscription == "T" then
subscription = "to"
else error("Unknown subscription type: "..subscription) end;
local ask = row.ask;
if ask == "N" then
ask = nil;
elseif ask == "O" then
ask = "subscribe";
elseif ask == "I" then
roster_pending(node, hostname, contact);
ask = nil;
elseif ask == "B" then
roster_pending(node, hostname, contact);
ask = "subscribe";
else error("Unknown ask type: "..ask); end
local item = {name = name, ask = ask, subscription = subscription, groups = {}};
roster(node, hostname, contact, item);
end
for _, row in ipairs(parsed["rostergroups"] or NULL) do
roster_group(row.username, hostname, row.jid, row.grp);
end
for _, row in ipairs(parsed["vcard"] or NULL) do
local stanza, err = parse_xml(row.vcard);
if stanza then
local ret, err = dm.store(row.username, hostname, "vcard", st.preserialize(stanza));
print("["..(err or "success").."] vCard: "..row.username.."@"..hostname);
else
print("[error] vCard XML parse failed: "..row.username.."@"..hostname);
end
end
for _, row in ipairs(parsed["private_storage"] or NULL) do
local stanza, err = parse_xml(row.data);
if stanza then
private_storage(row.username, hostname, row.namespace, stanza);
else
print("[error] Private XML parse failed: "..row.username.."@"..hostname);
end
end
table.sort(parsed["spool"] or NULL, function(a,b) return a.seq < b.seq; end); -- sort by sequence number, just in case
local time_offset = os.difftime(os.time(os.date("!*t")), os.time(os.date("*t"))) -- to deal with timezones
local date_parse = function(s)
local year, month, day, hour, min, sec = s:match("(....)-?(..)-?(..)T(..):(..):(..)");
return os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec-time_offset});
end
for _, row in ipairs(parsed["spool"] or NULL) do
local stanza, err = parse_xml(row.xml);
if stanza then
local last_child = stanza.tags[#stanza.tags];
if not last_child or last_child ~= stanza[#stanza] then error("Last child of offline message is not a tag"); end
if last_child.name ~= "x" and last_child.attr.xmlns ~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end
stanza[#stanza], stanza.tags[#stanza.tags] = nil, nil;
local t = date_parse(last_child.attr.stamp);
offline_msg(row.username, hostname, t, stanza);
else
print("[error] Offline message XML parsing failed: "..row.username.."@"..hostname);
end
end
prosody-0.10.0/tools/xep227toprosody.lua 0000755 0001750 0001750 00000021727 13163172043 020064 0 ustar matthew matthew #!/usr/bin/env lua
-- Prosody IM
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
-- Copyright (C) 2010 Stefan Gehn
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- FIXME: XEP-0227 supports XInclude but luaexpat does not
--
-- XEP-227 elements and their current level of support:
-- Hosts : supported
-- Users : supported
-- Rosters : supported, needs testing
-- Offline Messages : supported, needs testing
-- Private XML Storage : supported, needs testing
-- vCards : supported, needs testing
-- Privacy Lists: UNSUPPORTED
-- http://xmpp.org/extensions/xep-0227.html#privacy-lists
-- mod_privacy uses dm.load(username, host, "privacy"); and stores stanzas 1:1
-- Incoming Subscription Requests : supported
package.path = package.path..";../?.lua";
package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
-- ugly workaround for getting datamanager to work outside of prosody :(
prosody = { };
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
elseif package.config:sub(1,1) == "/" then
prosody.platform = "posix";
end
local lxp = require "lxp";
local st = require "util.stanza";
local xmppstream = require "util.xmppstream";
local new_xmpp_handlers = xmppstream.new_sax_handlers;
local dm = require "util.datamanager"
dm.set_data_path("data");
local ns_separator = xmppstream.ns_separator;
local ns_pattern = xmppstream.ns_pattern;
local xmlns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
-----------------------------------------------------------------------
function store_vcard(username, host, stanza)
-- create or update vCard for username@host
local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza));
print("["..(err or "success").."] stored vCard: "..username.."@"..host);
end
function store_password(username, host, password)
-- create or update account for username@host
local ret, err = dm.store(username, host, "accounts", {password = password});
print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
end
function store_roster(username, host, roster_items)
-- fetch current roster-table for username@host if he already has one
local roster = dm.load(username, host, "roster") or {};
-- merge imported roster-items with loaded roster
for item_tag in roster_items:childtags("item") do
-- jid for this roster-item
local item_jid = item_tag.attr.jid
-- validate item stanzas
if (item_jid ~= "") then
-- prepare roster item
-- TODO: is the subscription attribute optional?
local item = {subscription = item_tag.attr.subscription, groups = {}};
-- optional: give roster item a real name
if item_tag.attr.name then
item.name = item_tag.attr.name;
end
-- optional: iterate over group stanzas inside item stanza
for group_tag in item_tag:childtags("group") do
local group_name = group_tag:get_text();
if (group_name ~= "") then
item.groups[group_name] = true;
else
print("[error] invalid group stanza: "..group_tag:pretty_print());
end
end
-- store item in roster
roster[item_jid] = item;
print("[success] roster entry: " ..username.."@"..host.." - "..item_jid);
else
print("[error] invalid roster stanza: " ..item_tag:pretty_print());
end
end
-- store merged roster-table
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored roster: " ..username.."@"..host);
end
function store_private(username, host, private_items)
local private = dm.load(username, host, "private") or {};
for _, ch in ipairs(private_items.tags) do
--print("private :"..ch:pretty_print());
private[ch.name..":"..ch.attr.xmlns] = st.preserialize(ch);
print("[success] private item: " ..username.."@"..host.." - "..ch.name);
end
local ret, err = dm.store(username, host, "private", private);
print("["..(err or "success").."] stored private: " ..username.."@"..host);
end
function store_offline_messages(username, host, offline_messages)
-- TODO: maybe use list_load(), append and list_store() instead
-- of constantly reopening the file with list_append()?
for ch in offline_messages:childtags("message", "jabber:client") do
--print("message :"..ch:pretty_print());
local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch));
print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from);
end
end
function store_subscription_request(username, host, presence_stanza)
local from_bare = presence_stanza.attr.from;
-- fetch current roster-table for username@host if he already has one
local roster = dm.load(username, host, "roster") or {};
local item = roster[from_bare];
if item and (item.subscription == "from" or item.subscription == "both") then
return; -- already subscribed, do nothing
end
-- add to table of pending subscriptions
if not roster.pending then roster.pending = {}; end
roster.pending[from_bare] = true;
-- store updated roster-table
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare);
end
-----------------------------------------------------------------------
local curr_host = "";
local user_name = "";
local cb = {
stream_tag = "user",
stream_ns = xmlns_xep227,
};
function cb.streamopened(session, attr)
session.notopen = false;
user_name = attr.name;
store_password(user_name, curr_host, attr.password);
end
function cb.streamclosed(session)
session.notopen = true;
user_name = "";
end
function cb.handlestanza(session, stanza)
--print("Parsed stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
if (stanza.name == "vCard") and (stanza.attr.xmlns == "vcard-temp") then
store_vcard(user_name, curr_host, stanza);
elseif (stanza.name == "query") then
if (stanza.attr.xmlns == "jabber:iq:roster") then
store_roster(user_name, curr_host, stanza);
elseif (stanza.attr.xmlns == "jabber:iq:private") then
store_private(user_name, curr_host, stanza);
end
elseif (stanza.name == "offline-messages") then
store_offline_messages(user_name, curr_host, stanza);
elseif (stanza.name == "presence") and (stanza.attr.xmlns == "jabber:client") then
store_subscription_request(user_name, curr_host, stanza);
else
print("UNHANDLED stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
end
end
local user_handlers = new_xmpp_handlers({ notopen = true }, cb);
-----------------------------------------------------------------------
local lxp_handlers = {
--count = 0
};
-- TODO: error handling for invalid opening elements if curr_host is empty
function lxp_handlers.StartElement(parser, elementname, attributes)
local curr_ns, name = elementname:match(ns_pattern);
if name == "" then
curr_ns, name = "", curr_ns;
end
--io.write("+ ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
--count = count + 1;
if curr_host ~= "" then
-- forward to xmlhandlers
user_handlers.StartElement(parser, elementname, attributes);
elseif (curr_ns == xmlns_xep227) and (name == "host") then
curr_host = attributes["jid"]; -- start of host element
print("Begin parsing host "..curr_host);
elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
io.stderr:write("Unhandled XML element: ", name, "\n");
os.exit(1);
end
end
-- TODO: error handling for invalid closing elements if host is empty
function lxp_handlers.EndElement(parser, elementname)
local curr_ns, name = elementname:match(ns_pattern);
if name == "" then
curr_ns, name = "", curr_ns;
end
--count = count - 1;
--io.write("- ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
if curr_host ~= "" then
if (curr_ns == xmlns_xep227) and (name == "host") then
print("End parsing host "..curr_host);
curr_host = "" -- end of host element
else
-- forward to xmlhandlers
user_handlers.EndElement(parser, elementname);
end
elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
io.stderr:write("Unhandled XML element: ", name, "\n");
os.exit(1);
end
end
function lxp_handlers.CharacterData(parser, string)
if curr_host ~= "" then
-- forward to xmlhandlers
user_handlers.CharacterData(parser, string);
end
end
-----------------------------------------------------------------------
local arg = ...;
local help = "/? -? ? /h -h /help -help --help";
if not arg or help:find(arg, 1, true) then
print([[XEP-227 importer for Prosody
Usage: xep227toprosody.lua filename.xml
]]);
os.exit(1);
end
local file = io.open(arg);
if not file then
io.stderr:write("Could not open file: ", arg, "\n");
os.exit(0);
end
local parser = lxp.new(lxp_handlers, ns_separator);
for l in file:lines() do
parser:parse(l);
end
parser:parse();
parser:close();
file:close();
prosody-0.10.0/tools/migration/ 0000775 0001750 0001750 00000000000 13163172043 016326 5 ustar matthew matthew prosody-0.10.0/tools/migration/prosody-migrator.lua 0000644 0001750 0001750 00000006107 13163172043 022354 0 ustar matthew matthew #!/usr/bin/env lua
CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
-- Substitute ~ with path to home directory in paths
if CFG_CONFIGDIR then
CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME"));
end
if CFG_SOURCEDIR then
CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME"));
end
local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua";
-- Command-line parsing
local options = {};
local i = 1;
while arg[i] do
if arg[i]:sub(1,2) == "--" then
local opt, val = arg[i]:match("([%w-]+)=?(.*)");
if opt then
options[(opt:sub(3):gsub("%-", "_"))] = #val > 0 and val or true;
end
table.remove(arg, i);
else
i = i + 1;
end
end
if CFG_SOURCEDIR then
package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
else
package.path = "../../?.lua;"..package.path
package.cpath = "../../?.so;"..package.cpath
end
local envloadfile = require "util.envload".envloadfile;
local config_file = options.config or default_config;
local from_store = arg[1] or "input";
local to_store = arg[2] or "output";
config = {};
local config_env = setmetatable({}, { __index = function(t, k) return function(tbl) config[k] = tbl; end; end });
local config_chunk, err = envloadfile(config_file, config_env);
if not config_chunk then
print("There was an error loading the config file, check that the file exists");
print("and that the syntax is correct:");
print("", err);
os.exit(1);
end
config_chunk();
local have_err;
if #arg > 0 and #arg ~= 2 then
have_err = true;
print("Error: Incorrect number of parameters supplied.");
end
if not config[from_store] then
have_err = true;
print("Error: Input store '"..from_store.."' not found in the config file.");
end
if not config[to_store] then
have_err = true;
print("Error: Output store '"..to_store.."' not found in the config file.");
end
function load_store_handler(name)
local store_type = config[name].type;
if not store_type then
print("Error: "..name.." store type not specified in the config file");
return false;
else
local ok, err = pcall(require, "migrator."..store_type);
if not ok then
print(("Error: Failed to initialize '%s' store:\n\t%s")
:format(name, err));
return false;
end
end
return true;
end
have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output"));
if have_err then
print("");
print("Usage: "..arg[0].." FROM_STORE TO_STORE");
print("If no stores are specified, 'input' and 'output' are used.");
print("");
print("The available stores in your migrator config are:");
print("");
for store in pairs(config) do
print("", store);
end
print("");
os.exit(1);
end
local itype = config[from_store].type;
local otype = config[to_store].type;
local reader = require("migrator."..itype).reader(config[from_store]);
local writer = require("migrator."..otype).writer(config[to_store]);
local json = require "util.json";
io.stderr:write("Migrating...\n");
for x in reader do
--print(json.encode(x))
writer(x);
end
writer(nil); -- close
io.stderr:write("Done!\n");
prosody-0.10.0/tools/migration/Makefile 0000644 0001750 0001750 00000002513 13163172043 017765 0 ustar matthew matthew
include ../../config.unix
BIN = $(DESTDIR)$(PREFIX)/bin
CONFIG = $(DESTDIR)$(SYSCONFDIR)
SOURCE = $(DESTDIR)$(LIBDIR)/prosody
DATA = $(DESTDIR)$(DATADIR)
MAN = $(DESTDIR)$(PREFIX)/share/man
INSTALLEDSOURCE = $(LIBDIR)/prosody
INSTALLEDCONFIG = $(SYSCONFDIR)
INSTALLEDMODULES = $(LIBDIR)/prosody/modules
INSTALLEDDATA = $(DATADIR)
SOURCE_FILES = migrator/*.lua
all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua $(SOURCE_FILES)
install: prosody-migrator.install migrator.cfg.lua.install
install -d $(BIN) $(CONFIG) $(SOURCE) $(SOURCE)/migrator
install -d $(MAN)/man1
install -d $(SOURCE)/migrator
install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator
install -m644 $(SOURCE_FILES) $(SOURCE)/migrator
test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua
clean:
rm -f prosody-migrator.install
rm -f migrator.cfg.lua.install
prosody-migrator.install: prosody-migrator.lua
sed "1s/\blua\b/$(RUNWITH)/; \
s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \
s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|;" \
< prosody-migrator.lua > prosody-migrator.install
migrator.cfg.lua.install: migrator.cfg.lua
sed "s|^local data_path = .*;$$|local data_path = '$(INSTALLEDDATA)';|;" \
< migrator.cfg.lua > migrator.cfg.lua.install
prosody-0.10.0/tools/migration/migrator/ 0000775 0001750 0001750 00000000000 13163172043 020152 5 ustar matthew matthew prosody-0.10.0/tools/migration/migrator/jabberd14.lua 0000644 0001750 0001750 00000010620 13163172043 022410 0 ustar matthew matthew
local lfs = require "lfs";
local st = require "util.stanza";
local parse_xml = require "util.xml".parse;
local os_getenv = os.getenv;
local io_open = io.open;
local assert = assert;
local ipairs = ipairs;
local coroutine = coroutine;
local print = print;
local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end
local function is_file(path) return lfs.attributes(path, "mode") == "file"; end
local function clean_path(path)
return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~");
end
local function load_xml(path)
local f, err = io_open(path);
if not f then return f, err; end
local data = f:read("*a");
f:close();
if not data then return; end
return parse_xml(data);
end
local function load_spool_file(host, filename, path)
local xml = load_xml(path);
if not xml then return; end
local register_element = xml:get_child("query", "jabber:iq:register");
local username_element = register_element and register_element:get_child("username", "jabber:iq:register");
local password_element = register_element and register_element:get_child("password", "jabber:iq:auth");
local username = username_element and username_element:get_text();
local password = password_element and password_element:get_text();
if not username then
print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename)
return;
elseif username..".xml" ~= filename then
print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename);
return;
end
local userdata = {
user = username;
host = host;
stores = {};
};
local stores = userdata.stores;
stores.accounts = { password = password };
for i=1,#xml.tags do
local tag = xml.tags[i];
local xname = (tag.attr.xmlns or "")..":"..tag.name;
if tag.attr.j_private_flag == "1" and tag.attr.xmlns then
-- Private XML
stores.private = stores.private or {};
tag.attr.j_private_flag = nil;
stores.private[tag.attr.xmlns] = st.preserialize(tag);
elseif xname == "jabber:iq:auth:password" then
if stores.accounts.password ~= tag:get_text() then
if password then
print("[warn] conflicting passwords")
else
stores.accounts.password = tag:get_text();
end
end
elseif xname == "jabber:iq:register:query" then
-- already processed
elseif xname == "jabber:xdb:nslist:foo" then
-- ignore
elseif xname == "jabber:iq:auth:0k:zerok" then
-- ignore
elseif xname == "jabber:iq:roster:query" then
-- Roster
local roster = {};
local subscription_types = { from = true, to = true, both = true, none = true };
for _,item_element in ipairs(tag.tags) do
assert(item_element.name == "item");
assert(item_element.attr.jid);
assert(subscription_types[item_element.attr.subscription]);
assert((item_element.attr.ask or "subscribe") == "subscribe")
if item_element.name == "item" then
local groups = {};
for _,group_element in ipairs(item_element.tags) do
assert(group_element.name == "group");
groups[group_element:get_text()] = true;
end
local item = {
name = item_element.attr.name;
subscription = item_element.attr.subscription;
ask = item_element.attr.ask;
groups = groups;
};
roster[item_element.attr.jid] = item;
end
end
stores.roster = roster;
elseif xname == "jabber:iq:last:query" then
-- Last activity
elseif xname == "jabber:x:offline:foo" then
-- Offline messages
elseif xname == "vcard-temp:vCard" then
-- vCards
stores.vcard = st.preserialize(tag);
else
print("[warn] Unknown tag: "..xname);
end
end
return userdata;
end
local function loop_over_users(path, host, cb)
for file in lfs.dir(path) do
if file:match("%.xml$") then
local user = load_spool_file(host, file, path.."/"..file);
if user then cb(user); end
end
end
end
local function loop_over_hosts(path, cb)
for host in lfs.dir(path) do
if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then
loop_over_users(path.."/"..host, host, cb);
end
end
end
local function reader(input)
local path = clean_path(assert(input.path, "no input.path specified"));
assert(is_dir(path), "input.path is not a directory");
if input.host then
return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end);
else
return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end);
end
end
return {
reader = reader;
};
prosody-0.10.0/tools/migration/migrator/prosody_sql.lua 0000644 0001750 0001750 00000013567 13163172043 023245 0 ustar matthew matthew
local assert = assert;
local have_DBI = pcall(require,"DBI");
local print = print;
local type = type;
local next = next;
local pairs = pairs;
local t_sort = table.sort;
local json = require "util.json";
local mtools = require "migrator.mtools";
local tostring = tostring;
local tonumber = tonumber;
if not have_DBI then
error("LuaDBI (required for SQL support) was not found, please see http://prosody.im/doc/depends#luadbi", 0);
end
local sql = require "util.sql";
local function create_table(engine, name) -- luacheck: ignore 431/engine
local Table, Column, Index = sql.Table, sql.Column, sql.Index;
local ProsodyTable = Table {
name= name or "prosody";
Column { name="host", type="TEXT", nullable=false };
Column { name="user", type="TEXT", nullable=false };
Column { name="store", type="TEXT", nullable=false };
Column { name="key", type="TEXT", nullable=false };
Column { name="type", type="TEXT", nullable=false };
Column { name="value", type="MEDIUMTEXT", nullable=false };
Index { name="prosody_index", "host", "user", "store", "key" };
};
engine:transaction(function()
ProsodyTable:create(engine);
end);
end
local function serialize(value)
local t = type(value);
if t == "string" or t == "boolean" or t == "number" then
return t, tostring(value);
elseif t == "table" then
local value,err = json.encode(value);
if value then return "json", value; end
return nil, err;
end
return nil, "Unhandled value type: "..t;
end
local function deserialize(t, value)
if t == "string" then return value;
elseif t == "boolean" then
if value == "true" then return true;
elseif value == "false" then return false; end
elseif t == "number" then return tonumber(value);
elseif t == "json" then
return json.decode(value);
end
end
local function decode_user(item)
local userdata = {
user = item[1][1].user;
host = item[1][1].host;
stores = {};
};
for i=1,#item do -- loop over stores
local result = {};
local store = item[i];
for i=1,#store do -- loop over store data
local row = store[i];
local k = row.key;
local v = deserialize(row.type, row.value);
if k and v then
if k ~= "" then result[k] = v; elseif type(v) == "table" then
for a,b in pairs(v) do
result[a] = b;
end
end
end
userdata.stores[store[1].store] = result;
end
end
return userdata;
end
local function needs_upgrade(engine, params)
if params.driver == "MySQL" then
local success = engine:transaction(function()
local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'");
assert(result:rowcount() == 0);
-- COMPAT w/pre-0.10: Upgrade table to UTF-8 if not already
local check_encoding_query = [[
SELECT "COLUMN_NAME","COLUMN_TYPE","TABLE_NAME"
FROM "information_schema"."columns"
WHERE "TABLE_NAME" LIKE 'prosody%%' AND ( "CHARACTER_SET_NAME"!='%s' OR "COLLATION_NAME"!='%s_bin' );
]];
check_encoding_query = check_encoding_query:format(engine.charset, engine.charset);
local result = engine:execute(check_encoding_query);
assert(result:rowcount() == 0)
end);
if not success then
-- Upgrade required
return true;
end
end
return false;
end
local function reader(input)
local engine = assert(sql:create_engine(input, function (engine) -- luacheck: ignore 431/engine
if needs_upgrade(engine, input) then
error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade");
end
end));
local keys = {"host", "user", "store", "key", "type", "value"};
assert(engine:connect());
local f,s,val = assert(engine:select("SELECT \"host\", \"user\", \"store\", \"key\", \"type\", \"value\" FROM \"prosody\";"));
-- get SQL rows, sorted
local iter = mtools.sorted {
reader = function() val = f(s, val); return val; end;
filter = function(x)
for i=1,#keys do
x[ keys[i] ] = x[i];
end
if x.host == "" then x.host = nil; end
if x.user == "" then x.user = nil; end
if x.store == "" then x.store = nil; end
return x;
end;
sorter = function(a, b)
local a_host, a_user, a_store = a.host or "", a.user or "", a.store or "";
local b_host, b_user, b_store = b.host or "", b.user or "", b.store or "";
return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store);
end;
};
-- merge rows to get stores
iter = mtools.merged(iter, function(a, b)
return (a.host == b.host and a.user == b.user and a.store == b.store);
end);
-- merge stores to get users
iter = mtools.merged(iter, function(a, b)
return (a[1].host == b[1].host and a[1].user == b[1].user);
end);
return function()
local x = iter();
return x and decode_user(x);
end;
end
local function writer(output, iter)
local engine = assert(sql:create_engine(output, function (engine) -- luacheck: ignore 431/engine
if needs_upgrade(engine, output) then
error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade");
end
create_table(engine);
end));
assert(engine:connect());
assert(engine:delete("DELETE FROM \"prosody\""));
local insert_sql = "INSERT INTO \"prosody\" (\"host\",\"user\",\"store\",\"key\",\"type\",\"value\") VALUES (?,?,?,?,?,?)";
return function(item)
if not item then assert(engine.conn:commit()) return end -- end of input
local host = item.host or "";
local user = item.user or "";
for store, data in pairs(item.stores) do
-- TODO transactions
local extradata = {};
for key, value in pairs(data) do
if type(key) == "string" and key ~= "" then
local t, value = assert(serialize(value));
local ok, err = assert(engine:insert(insert_sql, host, user, store, key, t, value));
else
extradata[key] = value;
end
end
if next(extradata) ~= nil then
local t, extradata = assert(serialize(extradata));
local ok, err = assert(engine:insert(insert_sql, host, user, store, "", t, extradata));
end
end
end;
end
return {
reader = reader;
writer = writer;
}
prosody-0.10.0/tools/migration/migrator/prosody_files.lua 0000644 0001750 0001750 00000011051 13163172043 023532 0 ustar matthew matthew
local print = print;
local assert = assert;
local setmetatable = setmetatable;
local tonumber = tonumber;
local char = string.char;
local coroutine = coroutine;
local lfs = require "lfs";
local loadfile = loadfile;
local pcall = pcall;
local mtools = require "migrator.mtools";
local next = next;
local pairs = pairs;
local json = require "util.json";
local os_getenv = os.getenv;
local error = error;
prosody = {};
local dm = require "util.datamanager"
local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end
local function is_file(path) return lfs.attributes(path, "mode") == "file"; end
local function clean_path(path)
return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~");
end
local encode, decode; do
local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });
decode = function (s) return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); end
encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end
end
local function decode_dir(x)
if x:gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then
return decode(x);
end
end
local function decode_file(x)
if x:match(".%.dat$") and x:gsub("%.dat$", ""):gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then
return decode(x:gsub("%.dat$", ""));
end
end
local function prosody_dir(path, ondir, onfile, ...)
for x in lfs.dir(path) do
local xpath = path.."/"..x;
if decode_dir(x) and is_dir(xpath) then
ondir(xpath, x, ...);
elseif decode_file(x) and is_file(xpath) then
onfile(xpath, x, ...);
end
end
end
local function handle_root_file(path, name)
--print("root file: ", decode_file(name))
coroutine.yield { user = nil, host = nil, store = decode_file(name) };
end
local function handle_host_file(path, name, host)
--print("host file: ", decode_dir(host).."/"..decode_file(name))
coroutine.yield { user = nil, host = decode_dir(host), store = decode_file(name) };
end
local function handle_store_file(path, name, store, host)
--print("store file: ", decode_file(name).."@"..decode_dir(host).."/"..decode_dir(store))
coroutine.yield { user = decode_file(name), host = decode_dir(host), store = decode_dir(store) };
end
local function handle_host_store(path, name, host)
prosody_dir(path, function() end, handle_store_file, name, host);
end
local function handle_host_dir(path, name)
prosody_dir(path, handle_host_store, handle_host_file, name);
end
local function handle_root_dir(path)
prosody_dir(path, handle_host_dir, handle_root_file);
end
local function decode_user(item)
local userdata = {
user = item[1].user;
host = item[1].host;
stores = {};
};
for i=1,#item do -- loop over stores
local result = {};
local store = item[i];
userdata.stores[store.store] = store.data;
store.user = nil; store.host = nil; store.store = nil;
end
return userdata;
end
local function reader(input)
local path = clean_path(assert(input.path, "no input.path specified"));
assert(is_dir(path), "input.path is not a directory");
local iter = coroutine.wrap(function()handle_root_dir(path);end);
-- get per-user stores, sorted
local iter = mtools.sorted {
reader = function()
local x = iter();
while x do
dm.set_data_path(path);
local err;
x.data, err = dm.load(x.user, x.host, x.store);
if x.data == nil and err then
local p = dm.getpath(x.user, x.host, x.store);
print(("Error loading data at path %s for %s@%s (%s store): %s")
:format(p, x.user or "", x.host or "", x.store or "", err or ""));
else
return x;
end
x = iter();
end
end;
sorter = function(a, b)
local a_host, a_user, a_store = a.host or "", a.user or "", a.store or "";
local b_host, b_user, b_store = b.host or "", b.user or "", b.store or "";
return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store);
end;
};
-- merge stores to get users
iter = mtools.merged(iter, function(a, b)
return (a.host == b.host and a.user == b.user);
end);
return function()
local x = iter();
return x and decode_user(x);
end
end
local function writer(output)
local path = clean_path(assert(output.path, "no output.path specified"));
assert(is_dir(path), "output.path is not a directory");
return function(item)
if not item then return; end -- end of input
dm.set_data_path(path);
for store, data in pairs(item.stores) do
assert(dm.store(item.user, item.host, store, data));
end
end
end
return {
reader = reader;
writer = writer;
}
prosody-0.10.0/tools/migration/migrator/mtools.lua 0000644 0001750 0001750 00000002055 13163172043 022172 0 ustar matthew matthew
local print = print;
local t_insert = table.insert;
local t_sort = table.sort;
local function sorted(params)
local reader = params.reader; -- iterator to get items from
local sorter = params.sorter; -- sorting function
local filter = params.filter; -- filter function
local cache = {};
for item in reader do
if filter then item = filter(item); end
if item then t_insert(cache, item); end
end
if sorter then
t_sort(cache, sorter);
end
local i = 0;
return function()
i = i + 1;
return cache[i];
end;
end
local function merged(reader, merger)
local item1 = reader();
local merged = { item1 };
return function()
while true do
if not item1 then return nil; end
local item2 = reader();
if not item2 then item1 = nil; return merged; end
if merger(item1, item2) then
--print("merged")
item1 = item2;
t_insert(merged, item1);
else
--print("unmerged", merged)
item1 = item2;
local tmp = merged;
merged = { item1 };
return tmp;
end
end
end;
end
return {
sorted = sorted;
merged = merged;
}
prosody-0.10.0/tools/migration/migrator.cfg.lua 0000644 0001750 0001750 00000000533 13163172043 021412 0 ustar matthew matthew local data_path = "../../data";
input {
type = "prosody_files";
path = data_path;
}
output {
type = "prosody_sql";
driver = "SQLite3";
database = data_path.."/prosody.sqlite";
}
--[[
input {
type = "prosody_files";
path = data_path;
}
output {
type = "prosody_sql";
driver = "SQLite3";
database = data_path.."/prosody.sqlite";
}
]]