aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/array.lua36
-rw-r--r--util/async.lua158
-rw-r--r--util/caps.lua2
-rw-r--r--util/dataforms.lua12
-rw-r--r--util/datetime.lua2
-rw-r--r--util/debug.lua38
-rw-r--r--util/dependencies.lua20
-rw-r--r--util/events.lua79
-rw-r--r--util/filters.lua16
-rw-r--r--util/helpers.lua2
-rw-r--r--util/hex.lua25
-rw-r--r--util/hmac.lua2
-rw-r--r--util/import.lua2
-rw-r--r--util/interpolation.lua85
-rw-r--r--util/ip.lua54
-rw-r--r--util/iterators.lua46
-rw-r--r--util/jid.lua40
-rw-r--r--util/json.lua4
-rw-r--r--util/logger.lua2
-rw-r--r--util/mercurial.lua34
-rw-r--r--util/multitable.lua2
-rw-r--r--util/paths.lua44
-rw-r--r--util/pluginloader.lua2
-rw-r--r--util/prosodyctl.lua34
-rw-r--r--util/pubsub.lua90
-rw-r--r--util/queue.lua59
-rw-r--r--util/random.lua43
-rw-r--r--util/sasl.lua49
-rw-r--r--util/sasl/external.lua25
-rw-r--r--util/sasl/scram.lua145
-rw-r--r--util/sasl_cyrus.lua4
-rw-r--r--util/serialization.lua4
-rw-r--r--util/set.lua49
-rw-r--r--util/sql.lua190
-rw-r--r--util/sslconfig.lua87
-rw-r--r--util/stanza.lua16
-rw-r--r--util/statistics.lua160
-rw-r--r--util/termcolours.lua2
-rw-r--r--util/timer.lua4
-rw-r--r--util/uuid.lua46
-rw-r--r--util/x509.lua30
-rw-r--r--util/xml.lua7
-rw-r--r--util/xmppstream.lua43
43 files changed, 1397 insertions, 397 deletions
diff --git a/util/array.lua b/util/array.lua
index 2d58e7fb..b10396b1 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -11,8 +11,10 @@ local t_insert, t_sort, t_remove, t_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 = {};
@@ -59,13 +61,13 @@ function array_base.filter(outa, ina, func)
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
@@ -84,6 +86,25 @@ function array_base.pluck(outa, ina, key)
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(outa, ina)
local len = #self;
@@ -94,15 +115,6 @@ function array_methods:shuffle(outa, ina)
return self;
end
-function array_methods:reverse()
- local len = #self-1;
- for i=len,1,-1 do
- self:push(self[i]);
- self:pop(i);
- end
- return self;
-end
-
function array_methods:append(array)
local len,len2 = #self, #array;
for i=1,len2 do
diff --git a/util/async.lua b/util/async.lua
new file mode 100644
index 00000000..968ec804
--- /dev/null
+++ b/util/async.lua
@@ -0,0 +1,158 @@
+local log = require "util.logger".init("util.async");
+
+local function runner_continue(thread)
+ -- ASSUMPTION: runner is in 'waiting' state (but we don't have the runner to know for sure)
+ if coroutine.status(thread) ~= "suspended" then -- This should suffice
+ return false;
+ end
+ local ok, state, runner = coroutine.resume(thread);
+ if not ok then
+ local level = 0;
+ while debug.getinfo(thread, level, "") do level = level + 1; end
+ ok, runner = debug.getlocal(thread, level-1, 1);
+ local error_handler = runner.watchers.error;
+ if error_handler then error_handler(runner, debug.traceback(thread, state)); end
+ elseif state == "ready" then
+ -- If state is 'ready', it is our responsibility to update runner.state from 'waiting'.
+ -- We also have to :run(), because the queue might have further items that will not be
+ -- processed otherwise. FIXME: It's probably best to do this in a nexttick (0 timer).
+ runner.state = "ready";
+ runner:run();
+ end
+ return true;
+end
+
+local function waiter(num)
+ local thread = coroutine.running();
+ if not thread then
+ error("Not running in an async context, see http://prosody.im/doc/developers/async");
+ end
+ num = num or 1;
+ local waiting;
+ return function ()
+ if num == 0 then return; end -- already done
+ waiting = true;
+ coroutine.yield("wait");
+ end, function ()
+ num = num - 1;
+ if num == 0 and waiting then
+ runner_continue(thread);
+ elseif num < 0 then
+ error("done() called too many times");
+ end
+ end;
+end
+
+local function guarder()
+ local guards = {};
+ return function (id, func)
+ local thread = coroutine.running();
+ if not thread then
+ error("Not running in an async context, see http://prosody.im/doc/developers/async");
+ end
+ local guard = guards[id];
+ if not guard then
+ guard = {};
+ guards[id] = guard;
+ log("debug", "New guard!");
+ else
+ table.insert(guard, thread);
+ log("debug", "Guarded. %d threads waiting.", #guard)
+ coroutine.yield("wait");
+ end
+ local function exit()
+ local next_waiting = table.remove(guard, 1);
+ if next_waiting then
+ log("debug", "guard: Executing next waiting thread (%d left)", #guard)
+ runner_continue(next_waiting);
+ else
+ log("debug", "Guard off duty.")
+ guards[id] = nil;
+ end
+ end
+ if func then
+ func();
+ exit();
+ return;
+ end
+ return exit;
+ end;
+end
+
+local runner_mt = {};
+runner_mt.__index = runner_mt;
+
+local function runner_create_thread(func, self)
+ local thread = coroutine.create(function (self)
+ while true do
+ func(coroutine.yield("ready", self));
+ end
+ end);
+ assert(coroutine.resume(thread, self)); -- Start it up, it will return instantly to wait for the first input
+ return thread;
+end
+
+local empty_watchers = {};
+local function runner(func, watchers, data)
+ return setmetatable({ func = func, thread = false, state = "ready", notified_state = "ready",
+ queue = {}, watchers = watchers or empty_watchers, data = data }
+ , runner_mt);
+end
+
+function runner_mt:run(input)
+ if input ~= nil then
+ table.insert(self.queue, input);
+ end
+ if self.state ~= "ready" then
+ return true, self.state, #self.queue;
+ end
+
+ local q, thread = self.queue, self.thread;
+ if not thread or coroutine.status(thread) == "dead" then
+ thread = runner_create_thread(self.func, self);
+ self.thread = thread;
+ end
+
+ local n, state, err = #q, self.state, nil;
+ self.state = "running";
+ while n > 0 and state == "ready" do
+ local consumed;
+ for i = 1,n do
+ local input = q[i];
+ local ok, new_state = coroutine.resume(thread, input);
+ if not ok then
+ consumed, state, err = i, "ready", debug.traceback(thread, new_state);
+ self.thread = nil;
+ break;
+ elseif new_state == "wait" then
+ consumed, state = i, "waiting";
+ break;
+ end
+ end
+ if not consumed then consumed = n; end
+ if q[n+1] ~= nil then
+ n = #q;
+ end
+ for i = 1, n do
+ q[i] = q[consumed+i];
+ end
+ n = #q;
+ end
+ self.state = state;
+ if err or state ~= self.notified_state then
+ if err then
+ state = "error"
+ else
+ self.notified_state = state;
+ end
+ local handler = self.watchers[state];
+ if handler then handler(self, err); end
+ end
+ return true, state, n;
+end
+
+function runner_mt:enqueue(input)
+ table.insert(self.queue, input);
+end
+
+return { waiter = waiter, guarder = guarder, runner = runner };
diff --git a/util/caps.lua b/util/caps.lua
index a61e7403..4723b912 100644
--- a/util/caps.lua
+++ b/util/caps.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/dataforms.lua b/util/dataforms.lua
index ee37157a..9f2693b1 100644
--- a/util/dataforms.lua
+++ b/util/dataforms.lua
@@ -1,13 +1,13 @@
-- 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 pairs, ipairs = pairs, ipairs;
+local ipairs = ipairs;
local tostring, type, next = tostring, type, next;
local t_concat = table.concat;
local st = require "util.stanza";
@@ -32,13 +32,13 @@ function form_t.form(layout, data, formtype)
if layout.instructions then
form:tag("instructions"):text(layout.instructions):up();
end
- for n, field in ipairs(layout) do
+ 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
@@ -102,11 +102,11 @@ function form_t.form(layout, data, formtype)
end
form:up();
end
-
+
if field.required then
form:tag("required"):up();
end
-
+
-- Jump back up to list of fields
form:up();
end
diff --git a/util/datetime.lua b/util/datetime.lua
index a1f62a48..dd596527 100644
--- a/util/datetime.lua
+++ b/util/datetime.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/debug.lua b/util/debug.lua
index bff0e347..91f691e1 100644
--- a/util/debug.lua
+++ b/util/debug.lua
@@ -24,11 +24,15 @@ do
end
module("debugx", package.seeall);
-function get_locals_table(level)
- level = level + 1; -- Skip this function itself
+function get_locals_table(thread, level)
local locals = {};
for local_num = 1, math.huge do
- local name, value = debug.getlocal(level, local_num);
+ 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
@@ -88,19 +92,19 @@ function get_traceback_table(thread, start_level)
for level = start_level, math.huge do
local info;
if thread then
- info = debug.getinfo(thread, level+1);
+ 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(level+1);
+ locals = get_locals_table(thread, level+(thread and 0 or 1));
upvalues = get_upvalues_table(info.func);
};
- end
+ end
return levels;
end
@@ -134,15 +138,15 @@ function _traceback(thread, message, level)
return nil; -- debug.traceback() does this
end
- level = level or 1;
+ level = level or 0;
message = message and (message.."\n") or "";
-
- -- +3 counts for this function, and the pcall() and wrapper above us
- local levels = get_traceback_table(thread, level+3);
-
+
+ -- +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;
@@ -171,9 +175,11 @@ function _traceback(thread, message, level)
nlevel = nlevel-1;
table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line);
local npadding = (" "):rep(#tostring(nlevel));
- 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);
+ 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
diff --git a/util/dependencies.lua b/util/dependencies.lua
index 4d50cf63..ea19d9a8 100644
--- a/util/dependencies.lua
+++ b/util/dependencies.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -35,7 +35,7 @@ function missingdep(name, sources, msg)
print("");
end
--- COMPAT w/pre-0.8 Debian: The Debian config file used to use
+-- 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.
@@ -58,9 +58,9 @@ function check_dependencies()
end
local fatal;
-
+
local lxp = softreq "lxp"
-
+
if not lxp then
missingdep("luaexpat", {
["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0";
@@ -69,9 +69,9 @@ function check_dependencies()
});
fatal = true;
end
-
+
local socket = softreq "socket"
-
+
if not socket then
missingdep("luasocket", {
["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2";
@@ -80,7 +80,7 @@ function check_dependencies()
});
fatal = true;
end
-
+
local lfs, err = softreq "lfs"
if not lfs then
missingdep("luafilesystem", {
@@ -90,9 +90,9 @@ function check_dependencies()
});
fatal = true;
end
-
+
local ssl = softreq "ssl"
-
+
if not ssl then
missingdep("LuaSec", {
["Debian/Ubuntu"] = "http://prosody.im/download/start#debian_and_ubuntu";
@@ -100,7 +100,7 @@ function check_dependencies()
["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/";
}, "SSL/TLS support will not be available");
end
-
+
local encodings, err = softreq "util.encodings"
if not encodings then
if err:match("not found") then
diff --git a/util/events.lua b/util/events.lua
index 412acccd..d19da98b 100644
--- a/util/events.lua
+++ b/util/events.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -9,6 +9,7 @@
local pairs = pairs;
local t_insert = table.insert;
+local t_remove = table.remove;
local t_sort = table.sort;
local setmetatable = setmetatable;
local next = next;
@@ -17,6 +18,8 @@ module "events"
function new()
local handlers = {};
+ local global_wrappers;
+ local wrappers = {};
local event_map = {};
local function _rebuild_index(handlers, event)
local _handlers = event_map[event];
@@ -50,6 +53,9 @@ function new()
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);
@@ -60,20 +66,85 @@ function new()
remove_handler(event, handler);
end
end;
- local function fire_event(event, ...)
- local h = handlers[event];
+ 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](...);
+ 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 == nil 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;
diff --git a/util/filters.lua b/util/filters.lua
index 6290e53b..427dcb7c 100644
--- a/util/filters.lua
+++ b/util/filters.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -16,7 +16,7 @@ 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
@@ -28,11 +28,11 @@ function initialize(session)
return data;
end
end
-
+
for i=1,#new_filter_hooks do
new_filter_hooks[i](session);
end
-
+
return session.filter;
end
@@ -40,7 +40,7 @@ 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 = {};
@@ -48,14 +48,14 @@ function add_filter(session, type, callback, priority)
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
diff --git a/util/helpers.lua b/util/helpers.lua
index 08b86a7c..437a920c 100644
--- a/util/helpers.lua
+++ b/util/helpers.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/hex.lua b/util/hex.lua
new file mode 100644
index 00000000..e41f4863
--- /dev/null
+++ b/util/hex.lua
@@ -0,0 +1,25 @@
+local s_char = string.char;
+local s_format = string.format;
+local s_gsub = string.gsub;
+
+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, "..", hex_to_char));
+end
+
+return { to = to, from = from }
diff --git a/util/hmac.lua b/util/hmac.lua
index 51211c7a..2c4cc6ef 100644
--- a/util/hmac.lua
+++ b/util/hmac.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/import.lua b/util/import.lua
index 81401e8b..174da0ca 100644
--- a/util/import.lua
+++ b/util/import.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/interpolation.lua b/util/interpolation.lua
new file mode 100644
index 00000000..315cc203
--- /dev/null
+++ b/util/interpolation.lua
@@ -0,0 +1,85 @@
+-- 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;
+};
diff --git a/util/ip.lua b/util/ip.lua
index 856bf034..d0ae07eb 100644
--- a/util/ip.lua
+++ b/util/ip.lua
@@ -12,7 +12,17 @@ local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); 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 proto ~= "IPv4" and proto ~= "IPv6" then
+ 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
if proto == "IPv6" and ipStr:find('.', 1, true) then
@@ -82,7 +92,7 @@ local function v6scope(ip)
if ip:match("^[0:]*1$") then
return 0x2;
-- Link-local unicast:
- elseif ip:match("^[Ff][Ee][89ABab]") then
+ elseif ip:match("^[Ff][Ee][89ABab]") then
return 0x2;
-- Site-local unicast:
elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
@@ -192,5 +202,43 @@ function ip_methods:scope()
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 not bits then
+ return ipA == ipB;
+ end
+ if bits and ipB.proto == "IPv4" then
+ common_bits = common_bits - 96; -- v6 mapped addresses always share these bits
+ end
+ return common_bits >= bits;
+end
+
return {new_ip = new_ip,
- commonPrefixLength = commonPrefixLength};
+ commonPrefixLength = commonPrefixLength,
+ parse_cidr = parse_cidr,
+ match=match};
diff --git a/util/iterators.lua b/util/iterators.lua
index 1f6aacb8..aa9c3ec0 100644
--- a/util/iterators.lua
+++ b/util/iterators.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -10,6 +10,10 @@
local it = {};
+local t_insert = table.insert;
+local select, unpack, next = select, unpack, next;
+local function pack(...) return { n = select("#", ...), ... }; end
+
-- Reverse an iterator
function it.reverse(f, s, var)
local results = {};
@@ -19,9 +23,9 @@ function it.reverse(f, s, var)
local ret = { f(s, var) };
var = ret[1];
if var == nil then break; end
- table.insert(results, 1, ret);
+ t_insert(results, 1, ret);
end
-
+
-- Then return our reverse one
local i,max = 0, #results;
return function (results)
@@ -52,15 +56,15 @@ end
-- Given an iterator, iterate only over unique items
function it.unique(f, s, var)
local set = {};
-
+
return function ()
while true do
- local ret = { f(s, var) };
+ local ret = pack(f(s, var));
var = ret[1];
if var == nil then break; end
if not set[var] then
set[var] = true;
- return var;
+ return unpack(ret, 1, ret.n);
end
end
end;
@@ -69,14 +73,13 @@ end
--[[ Return the number of items an iterator returns ]]--
function it.count(f, s, var)
local x = 0;
-
+
while true do
- local ret = { f(s, var) };
- var = ret[1];
+ var = f(s, var);
if var == nil then break; end
x = x + 1;
end
-
+
return x;
end
@@ -104,7 +107,7 @@ end
function it.tail(n, f, s, var)
local results, count = {}, 0;
while true do
- local ret = { f(s, var) };
+ local ret = pack(f(s, var));
var = ret[1];
if var == nil then break; end
results[(count%n)+1] = ret;
@@ -117,9 +120,24 @@ function it.tail(n, f, s, var)
return function ()
pos = pos + 1;
if pos > n then return nil; end
- return unpack(results[((count-1+pos)%n)+1]);
+ local ret = results[((count-1+pos)%n)+1];
+ return unpack(ret, 1, ret.n);
end
- --return reverse(head(n, reverse(f, s, var)));
+ --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
@@ -139,7 +157,7 @@ function it.to_array(f, s, var)
while true do
var = f(s, var);
if var == nil then break; end
- table.insert(t, var);
+ t_insert(t, var);
end
return t;
end
diff --git a/util/jid.lua b/util/jid.lua
index 8e0a784c..27afab3e 100644
--- a/util/jid.lua
+++ b/util/jid.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -37,11 +37,7 @@ end
split = _split;
function bare(jid)
- local node, host = _split(jid);
- if node and host then
- return node.."@"..host;
- end
- return host;
+ return jid and match(jid, "^[^/]+");
end
local function _prepped_split(jid)
@@ -65,30 +61,22 @@ local function _prepped_split(jid)
end
prepped_split = _prepped_split;
-function prep(jid)
- local node, host, resource = _prepped_split(jid);
- if host then
- if node then
- host = node .. "@" .. host;
- end
- if resource then
- host = host .. "/" .. resource;
- end
- end
- return host;
-end
-
-function join(node, host, resource)
- if node and host and resource then
+local function _join(node, host, resource)
+ if not host then return end
+ if node and resource then
return node.."@"..host.."/"..resource;
- elseif node and host then
+ elseif node then
return node.."@"..host;
- elseif host and resource then
+ elseif resource then
return host.."/"..resource;
- elseif host then
- return host;
end
- return nil; -- Invalid JID
+ return host;
+end
+join = _join;
+
+function prep(jid)
+ local node, host, resource = _prepped_split(jid);
+ return _join(node, host, resource);
end
function compare(jid, acl)
diff --git a/util/json.lua b/util/json.lua
index 82ebcc43..a8a58afc 100644
--- a/util/json.lua
+++ b/util/json.lua
@@ -348,9 +348,9 @@ local first_escape = {
function json.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
diff --git a/util/logger.lua b/util/logger.lua
index 26206d4d..cd0769f9 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/mercurial.lua b/util/mercurial.lua
new file mode 100644
index 00000000..3f75c4c1
--- /dev/null
+++ b/util/mercurial.lua
@@ -0,0 +1,34 @@
+
+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;
diff --git a/util/multitable.lua b/util/multitable.lua
index dbf34d28..caf25118 100644
--- a/util/multitable.lua
+++ b/util/multitable.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/paths.lua b/util/paths.lua
new file mode 100644
index 00000000..89f4cad9
--- /dev/null
+++ b/util/paths.lua
@@ -0,0 +1,44 @@
+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;
diff --git a/util/pluginloader.lua b/util/pluginloader.lua
index 112c0d52..b9b3e207 100644
--- a/util/pluginloader.lua
+++ b/util/pluginloader.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua
index c6fe1986..9e30f8ea 100644
--- a/util/prosodyctl.lua
+++ b/util/prosodyctl.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -146,7 +146,7 @@ function adduser(params)
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;
@@ -162,7 +162,7 @@ function user_exists(params)
if not(provider) or provider.name == "null" then
usermanager.initialize_host(host);
end
-
+
return usermanager.user_exists(user, host);
end
@@ -170,7 +170,7 @@ function passwd(params)
if not _M.user_exists(params) then
return false, "no-such-user";
end
-
+
return _M.adduser(params);
end
@@ -179,7 +179,7 @@ function deluser(params)
return false, "no-such-user";
end
local user, host = nodeprep(params.user), nameprep(params.host);
-
+
return usermanager.delete_user(user, host);
end
@@ -192,30 +192,30 @@ function getpid()
if type(pidfile) ~= "string" then
return false, "invalid-pidfile";
end
-
- local modules_enabled = set.new(config.get("*", "modules_enabled"));
- if not modules_enabled:contains("posix") then
+
+ 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
@@ -256,10 +256,10 @@ function stop()
if not ret then
return false, "not-running";
end
-
+
local ok, pid = _M.getpid()
if not ok then return false, pid; end
-
+
signal.kill(pid, signal.SIGTERM);
return true;
end
@@ -272,10 +272,10 @@ function reload()
if not ret then
return false, "not-running";
end
-
+
local ok, pid = _M.getpid()
if not ok then return false, pid; end
-
+
signal.kill(pid, signal.SIGHUP);
return true;
end
diff --git a/util/pubsub.lua b/util/pubsub.lua
index e1418c62..c15a41d7 100644
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -1,23 +1,29 @@
local events = require "util.events";
+local t_remove = table.remove;
module("pubsub", package.seeall);
local service = {};
local service_mt = { __index = service };
-local default_config = {
+local default_config = { __index = {
broadcaster = function () end;
get_affiliation = function () end;
capabilities = {};
-};
+} };
+local default_node_config = { __index = {
+ ["pubsub#max_items"] = "20";
+} };
function new(config)
config = config or {};
return setmetatable({
- config = setmetatable(config, { __index = default_config });
+ 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
@@ -29,13 +35,13 @@ 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
@@ -47,7 +53,7 @@ function service:may(node, actor, action)
end
end
end
-
+
-- Check service-wide capabilities instead
local service_capabilities = self.config.capabilities;
local caps = service_capabilities[node_aff or service_aff];
@@ -57,7 +63,7 @@ function service:may(node, actor, action)
return can;
end
end
-
+
return false;
end
@@ -202,7 +208,7 @@ function service:get_subscription(node, actor, jid)
return true, node_obj.subscribers[jid];
end
-function service:create(node, actor)
+function service:create(node, actor, options)
-- Access checking
if not self:may(node, actor, "create") then
return false, "forbidden";
@@ -211,17 +217,20 @@ function service:create(node, actor)
if self.nodes[node] then
return false, "conflict";
end
-
+
+ self.data[node] = {};
self.nodes[node] = {
name = node;
subscribers = {};
- config = {};
- data = {};
+ config = setmetatable(options or {}, {__index=self.node_defaults});
affiliations = {};
};
+ setmetatable(self.nodes[node], { __index = { data = self.data[node] } }); -- COMPAT
+ 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
@@ -237,10 +246,31 @@ function service:delete(node, actor)
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
+local function remove_item_by_id(data, id)
+ if not data[id] then return end
+ data[id] = nil;
+ for i, _id in ipairs(data) do
+ if id == _id then
+ t_remove(data, i);
+ return i;
+ end
+ end
+end
+
+local function trim_items(data, max)
+ max = tonumber(max);
+ if not max or #data <= max then return end
+ repeat
+ data[t_remove(data, 1)] = nil;
+ until #data <= max
+end
+
function service:publish(node, actor, id, item)
-- Access checking
if not self:may(node, actor, "publish") then
@@ -258,9 +288,13 @@ function service:publish(node, actor, id, item)
end
node_obj = self.nodes[node];
end
- node_obj.data[id] = item;
+ local node_data = self.data[node];
+ remove_item_by_id(node_data, id);
+ node_data[#node_data + 1] = id;
+ node_data[id] = item;
+ trim_items(node_data, node_obj.config["pubsub#max_items"]);
self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
- self.config.broadcaster("items", node, node_obj.subscribers, item);
+ self.config.broadcaster("items", node, node_obj.subscribers, item, actor);
return true;
end
@@ -271,10 +305,11 @@ function service:retract(node, actor, id, retract)
end
--
local node_obj = self.nodes[node];
- if (not node_obj) or (not node_obj.data[id]) then
+ if (not node_obj) or (not self.data[node][id]) then
return false, "item-not-found";
end
- node_obj.data[id] = nil;
+ self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
+ remove_item_by_id(self.data[node], id);
if retract then
self.config.broadcaster("items", node, node_obj.subscribers, retract);
end
@@ -291,7 +326,8 @@ function service:purge(node, actor, notify)
if not node_obj then
return false, "item-not-found";
end
- node_obj.data = {}; -- Purge
+ self.data[node] = {}; -- Purge
+ self.events.fire_event("node-purged", { node = node, actor = actor });
if notify then
self.config.broadcaster("purge", node, node_obj.subscribers);
end
@@ -309,9 +345,9 @@ function service:get_items(node, actor, id)
return false, "item-not-found";
end
if id then -- Restrict results to a single specific item
- return true, { [id] = node_obj.data[id] };
+ return true, { id, [id] = self.data[node][id] };
else
- return true, node_obj.data;
+ return true, self.data[node];
end
end
@@ -388,4 +424,22 @@ function service:set_node_capabilities(node, actor, 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
+ trim_items(self.data[node], node_obj.config["pubsub#max_items"]);
+
+ return true;
+end
+
return _M;
diff --git a/util/queue.lua b/util/queue.lua
new file mode 100644
index 00000000..203da0e3
--- /dev/null
+++ b/util/queue.lua
@@ -0,0 +1,59 @@
+-- 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
+
+ return {
+ 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;
+ };
+end
+
+return {
+ new = new;
+};
+
diff --git a/util/random.lua b/util/random.lua
new file mode 100644
index 00000000..5938a94f
--- /dev/null
+++ b/util/random.lua
@@ -0,0 +1,43 @@
+-- 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 tostring = tostring;
+local os_time = os.time;
+local os_clock = os.clock;
+local ceil = math.ceil;
+local H = require "util.hashes".sha512;
+
+local last_uniq_time = 0;
+local function uniq_time()
+ local new_uniq_time = os_time();
+ if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end
+ last_uniq_time = new_uniq_time;
+ return new_uniq_time;
+end
+
+local function new_random(x)
+ return H(x..os_clock()..tostring({}));
+end
+
+local buffer = new_random(uniq_time());
+
+local function seed(x)
+ buffer = new_random(buffer..x);
+end
+
+local function bytes(n)
+ if #buffer < n+4 then seed(uniq_time()); end
+ local r = buffer:sub(1, n);
+ buffer = buffer:sub(n+1);
+ return r;
+end
+
+return {
+ seed = seed;
+ bytes = bytes;
+};
diff --git a/util/sasl.lua b/util/sasl.lua
index afb3861b..b91e29a6 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -27,19 +27,38 @@ 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
-function registerMechanism(name, backends, f)
+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);
@@ -63,6 +82,15 @@ function new(realm, profile)
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)
@@ -70,7 +98,23 @@ end
-- get a list of possible SASL mechanims to use
function method:mechanisms()
- return self.mechs;
+ 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
@@ -92,5 +136,6 @@ 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 _M;
diff --git a/util/sasl/external.lua b/util/sasl/external.lua
new file mode 100644
index 00000000..4c5c4343
--- /dev/null
+++ b/util/sasl/external.lua
@@ -0,0 +1,25 @@
+local saslprep = require "util.encodings".stringprep.saslprep;
+
+module "sasl.external"
+
+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
+
+function init(registerMechanism)
+ registerMechanism("EXTERNAL", {"external"}, external);
+end
+
+return _M;
diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua
index cf2f0ede..0d2852bf 100644
--- a/util/sasl/scram.lua
+++ b/util/sasl/scram.lua
@@ -13,7 +13,6 @@
local s_match = string.match;
local type = type
-local string = string
local base64 = require "util.encodings".base64;
local hmac_sha1 = require "util.hashes".hmac_sha1;
local sha1 = require "util.hashes".sha1;
@@ -39,18 +38,14 @@ scram_{MECH}:
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 function bp( b )
- local result = ""
- for i=1, b:len() do
- result = result.."\\"..b:byte(i)
- end
- return result
-end
-
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 = {};
@@ -73,11 +68,11 @@ local function validate_username(username, _nodeprep)
return false
end
end
-
+
-- replace =2C with , and =3D with =
username = username:gsub("=2C", ",");
username = username:gsub("=3D", "=");
-
+
-- apply SASLprep
username = saslprep(username);
@@ -106,96 +101,131 @@ function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
end
local function scram_gen(hash_name, H_f, HMAC_f)
+ local profile_name = "scram_" .. hashprep(hash_name);
local function scram_hash(self, message)
- if not self.state then self["state"] = {} end
-
+ 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
- if not self.state.name then
+ 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
- self.state["client_first_message"] = client_first_message;
- self.state["gs2_cbind_flag"], self.state["authzid"], self.state["name"], self.state["clientnonce"]
- = client_first_message:match("^(%a),(.*),n=(.*),r=([^,]*).*");
+ 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=([^,]*),?.*)$");
- -- we don't do any channel binding yet
- if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then
+ if not gs2_cbind_flag then
return "failure", "malformed-request";
end
- if not self.state.name or not self.state.clientnonce then
- return "failure", "malformed-request", "Channel binding isn't support at this time.";
+ 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
-
- self.state.name = validate_username(self.state.name, self.profile.nodeprep);
- if not self.state.name then
+
+ 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.state["servernonce"] = generate_uuid();
-
+
-- retreive credentials
+ local stored_key, server_key, salt, iteration_count;
if self.profile.plain then
- local password, state = self.profile.plain(self, self.state.name, self.realm)
+ local password, state = self.profile.plain(self, username, self.realm)
if state == nil then return "failure", "not-authorized"
elseif state == 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
- self.state.salt = generate_uuid();
- self.state.iteration_count = default_i;
+ salt = generate_uuid();
+ iteration_count = default_i;
local succ = false;
- succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+ succ, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
if not succ then
- log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
+ log("error", "Generating authentication database failed. Reason: %s", stored_key);
return "failure", "temporary-auth-failure";
end
- elseif self.profile["scram_"..hashprep(hash_name)] then
- local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self, self.state.name, self.realm);
+ elseif self.profile[profile_name] then
+ local state;
+ stored_key, server_key, iteration_count, salt, state = self.profile[profile_name](self, username, self.realm);
if state == nil then return "failure", "not-authorized"
elseif state == false then return "failure", "account-disabled" end
-
- self.state.stored_key = stored_key;
- self.state.server_key = server_key;
- self.state.iteration_count = iteration_count;
- self.state.salt = salt
end
-
- local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count;
- self.state["server_first_message"] = server_first_message;
+
+ 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;
-
- self.state["channelbinding"], self.state["nonce"], self.state["proof"] = client_final_message:match("^c=(.*),r=(.*),.*p=(.*)");
-
- if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
+
+ 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
- if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then
+ 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 = self.state.server_key;
- local StoredKey = self.state.stored_key;
-
- local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
+
+ 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(self.state.proof))
+ 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);
- self["username"] = self.state.name;
+ self["username"] = state.username;
return "success", server_final_message;
else
return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
@@ -208,6 +238,9 @@ end
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);
diff --git a/util/sasl_cyrus.lua b/util/sasl_cyrus.lua
index 19684587..a0e8bd69 100644
--- a/util/sasl_cyrus.lua
+++ b/util/sasl_cyrus.lua
@@ -78,10 +78,10 @@ local function init(service_name)
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.
+-- 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).
+-- is the default).
function new(realm, service_name, app_name, host_fqdn)
init(app_name or service_name);
diff --git a/util/serialization.lua b/util/serialization.lua
index 8a259184..04901135 100644
--- a/util/serialization.lua
+++ b/util/serialization.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -11,11 +11,9 @@ local type = type;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
-local error = error;
local pairs = pairs;
local next = next;
-local loadstring = loadstring;
local pcall = pcall;
local debug_traceback = debug.traceback;
diff --git a/util/set.lua b/util/set.lua
index fa065a9c..4be39c17 100644
--- a/util/set.lua
+++ b/util/set.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -34,19 +34,19 @@ function set_mt.__div(set, func)
return new_set;
end
function set_mt.__eq(set1, set2)
- local set1, set2 = set1._items, set2._items;
+ 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)
@@ -65,31 +65,34 @@ end
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 items;
+ return next, items;
end
-
+
function set:remove(item)
items[item] = nil;
end
-
- function set:add_list(list)
- if list then
- for _, item in ipairs(list) do
+
+ 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;
@@ -101,22 +104,22 @@ function new(list)
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
function union(set1, set2)
local set = new();
local items = set._items;
-
+
for item in pairs(set1._items) do
items[item] = true;
end
@@ -124,14 +127,14 @@ function union(set1, set2)
for item in pairs(set2._items) do
items[item] = true;
end
-
+
return set;
end
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
@@ -142,13 +145,13 @@ end
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
diff --git a/util/sql.lua b/util/sql.lua
index f360d6d0..be34c381 100644
--- a/util/sql.lua
+++ b/util/sql.lua
@@ -25,34 +25,9 @@ function is_column(x) return getmetatable(x)==column_mt; end
function is_index(x) return getmetatable(x)==index_mt; end
function is_table(x) return getmetatable(x)==table_mt; end
function is_query(x) return getmetatable(x)==query_mt; end
---function is_op(x) return getmetatable(x)==op_mt; end
---function expr(...) return setmetatable({...}, op_mt); end
function Integer(n) return "Integer()" end
function String(n) return "String()" end
---[[local ops = {
- __add = function(a, b) return "("..a.."+"..b..")" end;
- __sub = function(a, b) return "("..a.."-"..b..")" end;
- __mul = function(a, b) return "("..a.."*"..b..")" end;
- __div = function(a, b) return "("..a.."/"..b..")" end;
- __mod = function(a, b) return "("..a.."%"..b..")" end;
- __pow = function(a, b) return "POW("..a..","..b..")" end;
- __unm = function(a) return "NOT("..a..")" end;
- __len = function(a) return "COUNT("..a..")" end;
- __eq = function(a, b) return "("..a.."=="..b..")" end;
- __lt = function(a, b) return "("..a.."<"..b..")" end;
- __le = function(a, b) return "("..a.."<="..b..")" end;
-};
-
-local functions = {
-
-};
-
-local cmap = {
- [Integer] = Integer();
- [String] = String();
-};]]
-
function Column(definition)
return setmetatable(definition, column_mt);
end
@@ -94,7 +69,6 @@ function index_mt:__tostring()
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)
@@ -121,32 +95,13 @@ local function parse_url(url)
};
end
---[[local session = {};
-
-function session.query(...)
- local rets = {...};
- local query = setmetatable({ __rets = rets, __filters }, query_mt);
- return query;
-end
---
-
-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 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 dbh, err = DBI.Connect(
params.driver, params.database,
params.username, params.password,
@@ -156,8 +111,19 @@ function engine:connect()
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:execute(sql, ...)
local success, err = self:connect();
if not success then return success, err; end
@@ -177,8 +143,8 @@ function engine:execute(sql, ...)
end
local result_mt = { __index = {
- affected = function(self) return self.__affected; end;
- rowcount = function(self) return self.__rowcount; end;
+ affected = function(self) return self.__stmt:affected(); end;
+ rowcount = function(self) return self.__stmt:rowcount(); end;
} };
function engine:execute_query(sql, ...)
@@ -200,7 +166,7 @@ function engine:execute_update(sql, ...)
prepared[sql] = stmt;
end
assert(stmt:execute(...));
- return setmetatable({ __affected = stmt:affected(), __rowcount = stmt:rowcount() }, result_mt);
+ return setmetatable({ __stmt = stmt }, result_mt);
end
engine.insert = engine.execute_update;
engine.select = engine.execute_query;
@@ -208,8 +174,8 @@ engine.delete = engine.execute_update;
engine.update = engine.execute_update;
function engine:_transaction(func, ...)
if not self.conn then
- local a,b = self:connect();
- if not a then return a,b; end
+ 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("#", ...);
@@ -229,15 +195,15 @@ function engine:_transaction(func, ...)
end
end
function engine:transaction(...)
- local a,b = self:_transaction(...);
- if not a then
+ local ok, ret = self:_transaction(...);
+ if not ok then
local conn = self.conn;
if not conn or not conn:ping() then
self.conn = nil;
- a,b = self:_transaction(...);
+ ok, ret = self:_transaction(...);
end
end
- return a,b;
+ return ok, ret;
end
function engine:_create_index(index)
local sql = "CREATE INDEX `"..index.name.."` ON `"..index.table.."` (";
@@ -251,19 +217,39 @@ function engine:_create_index(index)
elseif self.params.driver == "MySQL" then
sql = sql:gsub("`([,)])", "`(20)%1");
end
+ if index.unique then
+ sql = sql:gsub("^CREATE", "CREATE UNIQUE");
+ end
--print(sql);
return self:execute(sql);
end
function engine:_create_table(table)
local sql = "CREATE TABLE `"..table.name.."` (";
for i,col in ipairs(table.c) do
- sql = sql.."`"..col.name.."` "..col.type;
+ 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 == "PostgreSQL" then
sql = sql:gsub("`", "\"");
+ elseif self.params.driver == "MySQL" then
+ sql = sql:gsub(";$", (" CHARACTER SET '%s' COLLATE '%s_bin';"):format(self.charset, self.charset));
end
local success,err = self:execute(sql);
if not success then return success,err; end
@@ -274,9 +260,49 @@ function engine:_create_table(table)
end
return success;
end
+function engine:set_encoding() -- to UTF-8
+ local driver = self.params.driver;
+ if driver == "SQLite3" then
+ return self:transaction(function()
+ if self:select"PRAGMA encoding;"()[1] == "UTF-8" then
+ self.charset = "utf8";
+ end
+ end);
+ end
+ local set_names_query = "SET NAMES '%s';"
+ local charset = "utf8";
+ if driver == "MySQL" then
+ local ok, charsets = self:transaction(function()
+ return self:select"SELECT `CHARACTER_SET_NAME` FROM `information_schema`.`CHARACTER_SETS` WHERE `CHARACTER_SET_NAME` LIKE 'utf8%' ORDER BY MAXLEN DESC LIMIT 1;";
+ end);
+ local row = ok and charsets();
+ charset = row and row[1] or charset;
+ 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);
+ 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);
+ return false, "Failed to set connection encoding";
+ end
+ end
+ end
+
+ return true;
+end
local engine_mt = { __index = engine };
-local function db2uri(params)
+function db2uri(params)
return build_url{
scheme = params.driver,
user = params.username,
@@ -286,55 +312,9 @@ local function db2uri(params)
path = params.database,
};
end
-local engine_cache = {}; -- TODO make weak valued
-function create_engine(self, params)
- local url = db2uri(params);
- if not engine_cache[url] then
- local engine = setmetatable({ url = url, params = params }, engine_mt);
- engine_cache[url] = engine;
- end
- return engine_cache[url];
-end
-
-
---[[Users = Table {
- name="users";
- Column { name="user_id", type=String(), primary_key=true };
-};
-print(Users)
-print(Users.c.user_id)]]
-
---local engine = create_engine('postgresql://scott:tiger@localhost:5432/mydatabase');
---[[local engine = create_engine{ driver = "SQLite3", database = "./alchemy.sqlite" };
-
-local i = 0;
-for row in assert(engine:execute("select * from sqlite_master")):rows(true) do
- i = i+1;
- print(i);
- for k,v in pairs(row) do
- print("",k,v);
- end
-end
-print("---")
-
-Prosody = Table {
- name="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="TEXT", nullable=false };
- Index { name="prosody_index", "host", "user", "store", "key" };
-};
---print(Prosody);
-assert(engine:transaction(function()
- assert(Prosody:create(engine));
-end));
-for row in assert(engine:execute("select user from prosody")):rows(true) do
- print("username:", row['username'])
+function create_engine(self, params, onconnect)
+ return setmetatable({ url = db2uri(params), params = params, onconnect = onconnect }, engine_mt);
end
---result.close();]]
return _M;
diff --git a/util/sslconfig.lua b/util/sslconfig.lua
new file mode 100644
index 00000000..42ce37d7
--- /dev/null
+++ b/util/sslconfig.lua
@@ -0,0 +1,87 @@
+
+local handlers = { };
+local finalisers = { };
+local id = function (v) return v end
+
+function handlers.options(a, k, b)
+ local o = a[k] or { };
+ if type(b) ~= "table" then b = { b } end
+ for key, value in pairs(b) do
+ if value == true or value == false then
+ o[key] = value;
+ else
+ o[value] = true;
+ end
+ end
+ a[k] = o;
+end
+
+handlers.verify = handlers.options;
+handlers.verifyext = handlers.options;
+
+function finalisers.options(a)
+ local o = {};
+ for opt, enable in pairs(a) do
+ if enable then
+ o[#o+1] = opt;
+ end
+ end
+ return o;
+end
+
+finalisers.verify = finalisers.options;
+finalisers.verifyext = finalisers.options;
+
+function finalisers.ciphers(a)
+ if type(a) == "table" then
+ return table.concat(a, ":");
+ end
+ return a;
+end
+
+local protocols = { "sslv2", "sslv3", "tlsv1", "tlsv1_1", "tlsv1_2" };
+for i = 1, #protocols do protocols[protocols[i] .. "+"] = i - 1; end
+
+local function protocol(a)
+ local min_protocol = protocols[a.protocol];
+ if min_protocol then
+ a.protocol = "sslv23";
+ for i = 1, min_protocol do
+ table.insert(a.options, "no_"..protocols[i]);
+ end
+ end
+end
+
+local function apply(a, b)
+ if type(b) == "table" then
+ for k,v in pairs(b) do
+ (handlers[k] or rawset)(a, k, v);
+ end
+ end
+end
+
+local function final(a)
+ local f = { };
+ for k,v in pairs(a) do
+ f[k] = (finalisers[k] or id)(v);
+ end
+ protocol(f);
+ return f;
+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;
+};
diff --git a/util/stanza.lua b/util/stanza.lua
index 7c214210..82601e63 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -99,7 +99,7 @@ function stanza_mt:get_child(name, xmlns)
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
@@ -152,7 +152,7 @@ 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
@@ -258,13 +258,13 @@ end
function stanza_mt.get_error(stanza)
local type, condition, text;
-
+
local error_tag = stanza:get_child("error");
if not error_tag then
return nil, nil, nil;
end
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
@@ -333,7 +333,7 @@ function deserialize(stanza)
stanza.tags = tags;
end
end
-
+
return stanza;
end
@@ -390,7 +390,7 @@ if do_pretty_printing then
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, ">");
@@ -411,7 +411,7 @@ if do_pretty_printing then
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
diff --git a/util/statistics.lua b/util/statistics.lua
new file mode 100644
index 00000000..26355026
--- /dev/null
+++ b/util/statistics.lua
@@ -0,0 +1,160 @@
+local t_sort = table.sort
+local m_floor = math.floor;
+local time = require "socket".gettime;
+
+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;
+}
diff --git a/util/termcolours.lua b/util/termcolours.lua
index 6ef3b689..ef978364 100644
--- a/util/termcolours.lua
+++ b/util/termcolours.lua
@@ -1,7 +1,7 @@
-- 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.
--
diff --git a/util/timer.lua b/util/timer.lua
index af1e57b6..0e10e144 100644
--- a/util/timer.lua
+++ b/util/timer.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -42,7 +42,7 @@ if not server.event then
end
new_data = {};
end
-
+
local next_time = math_huge;
for i, d in pairs(data) do
local t, callback = d[1], d[2];
diff --git a/util/uuid.lua b/util/uuid.lua
index 796c8ee4..e10fc0f7 100644
--- a/util/uuid.lua
+++ b/util/uuid.lua
@@ -1,50 +1,32 @@
-- 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 m_random = math.random;
-local tostring = tostring;
-local os_time = os.time;
-local os_clock = os.clock;
-local sha1 = require "util.hashes".sha1;
-
-module "uuid"
-
-local last_uniq_time = 0;
-local function uniq_time()
- local new_uniq_time = os_time();
- if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end
- last_uniq_time = new_uniq_time;
- return new_uniq_time;
-end
-
-local function new_random(x)
- return sha1(x..os_clock()..tostring({}), true);
-end
-
-local buffer = new_random(uniq_time());
-local function _seed(x)
- buffer = new_random(buffer..x);
-end
local function get_nibbles(n)
- if #buffer < n then _seed(uniq_time()); end
- local r = buffer:sub(0, n);
- buffer = buffer:sub(n+1);
- return r;
+ return hex(random_bytes(m_ceil(n/2))):sub(1, n);
end
+
local function get_twobits()
return ("%x"):format(get_nibbles(1):byte() % 4 + 8);
end
-function generate()
+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
-seed = _seed;
-return _M;
+return {
+ get_nibbles=get_nibbles;
+ generate = generate ;
+ -- COMPAT
+ seed = random.seed;
+};
diff --git a/util/x509.lua b/util/x509.lua
index 19d4ec6d..bf8d3906 100644
--- a/util/x509.lua
+++ b/util/x509.lua
@@ -20,11 +20,9 @@
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 pairs, ipairs = pairs, ipairs;
local s_format = string.format;
-local t_insert = table.insert;
-local t_concat = table.concat;
module "x509"
@@ -150,6 +148,9 @@ local function compare_srvname(host, service, asserted_names)
end
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];
@@ -161,7 +162,9 @@ function verify_identity(host, service, cert)
if sans[oid_xmppaddr] then
had_supported_altnames = true
- if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end
+ 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
@@ -212,4 +215,23 @@ function verify_identity(host, service, cert)
return false
end
+local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+
+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"
+
+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 _M;
diff --git a/util/xml.lua b/util/xml.lua
index 076490fa..88ccedde 100644
--- a/util/xml.lua
+++ b/util/xml.lua
@@ -11,6 +11,7 @@ local parse_xml = (function()
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)
@@ -26,8 +27,8 @@ local parse_xml = (function()
attr[i] = nil;
local ns, nm = k:match(ns_pattern);
if nm ~= "" then
- ns = ns_prefixes[ns];
- if ns then
+ ns = ns_prefixes[ns];
+ if ns then
attr[ns..":"..nm] = attr[k];
attr[k] = nil;
end
@@ -38,7 +39,7 @@ local parse_xml = (function()
function handler:CharacterData(data)
stanza:text(data);
end
- function handler:EndElement(tagname)
+ function handler:EndElement()
stanza:up();
end
local parser = lxp.new(handler, "\1");
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
index 138c86b7..dede0da9 100644
--- a/util/xmppstream.lua
+++ b/util/xmppstream.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -47,22 +47,22 @@ local function dummy_cb() end
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;
@@ -82,7 +82,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
attr.xmlns = curr_ns;
non_streamns_depth = non_streamns_depth + 1;
end
-
+
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
@@ -92,7 +92,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
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();
@@ -116,7 +116,7 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
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
@@ -205,22 +205,22 @@ function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
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
@@ -241,6 +241,25 @@ function new(session, stream_callbacks, stanza_size_limit)
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("<?xml version='1.0'?>");
+ send(st.stanza("stream:stream", attr):top_tag());
+ return true;
+ end
+
return {
reset = function ()
parser = new_parser(handlers, ns_separator, false);