From 4809855ead4bd71db4526869c9164bd8349a6dcf Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 5 Apr 2013 13:06:26 +0100 Subject: portmanager: import pairs() (thanks Maranda) --- core/portmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 1b3740cf..1b767a09 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -9,7 +9,7 @@ local set = require "util.set"; local table = table; local setmetatable, rawset, rawget = setmetatable, rawset, rawget; -local type, tonumber, tostring, ipairs = type, tonumber, tostring, ipairs; +local type, tonumber, tostring, ipairs, pairs = type, tonumber, tostring, ipairs, pairs; local prosody = prosody; local fire_event = prosody.events.fire_event; -- cgit v1.2.3 From 83221358fac655171bd91a2c8bd144e60767225d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 5 Apr 2013 19:13:46 +0100 Subject: moduleapi: assert() that prosody.core_post_stanza is not nil --- core/moduleapi.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index de900bf0..fa20c3cd 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -21,7 +21,10 @@ local tonumber, tostring = tonumber, tostring; local prosody = prosody; local hosts = prosody.hosts; -local core_post_stanza = prosody.core_post_stanza; + +-- FIXME: This assert() is to try and catch an obscure bug (2013-04-05) +local core_post_stanza = assert(prosody.core_post_stanza, + "prosody.core_post_stanza is nil, please report this as a bug"); -- Registry of shared module data local shared_data = setmetatable({}, { __mode = "v" }); -- cgit v1.2.3 From 616a9c6aba991468dcf917d19d92e40bfb4f3d3d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 5 Apr 2013 19:59:48 +0100 Subject: util.stanza: Use correct index when replacing the tag in .tags (thanks daurnimator) --- util/stanza.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/stanza.lua b/util/stanza.lua index 59c88c4e..7c214210 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -166,7 +166,7 @@ function stanza_mt:maptags(callback) curr_tag = curr_tag - 1; else self[i] = ret; - tags[i] = ret; + tags[curr_tag] = ret; end curr_tag = curr_tag + 1; end -- cgit v1.2.3 From 4527592c9153fa9ac49a754c7066a50d60080315 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 6 Apr 2013 12:20:31 +0100 Subject: util.json: Convert \uXXXX to UTF-8 when decoding --- util/json.lua | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/util/json.lua b/util/json.lua index abb87eab..ff7351a7 100644 --- a/util/json.lua +++ b/util/json.lua @@ -1,3 +1,12 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- utf8char copyright (C) 2007 Rici Lake +-- +-- 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; @@ -29,6 +38,32 @@ for i=0,31 do if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end end +local function utf8char(i) + if i >= 0 then + i = i - i%1 + if i < 128 then + return s_char(i) + else + local c1 = i % 64 + i = (i - c1) / 64 + if i < 32 then + return s_char(0xC0+i, 0x80+c1) + else + local c2 = i % 64 + i = (i - c2) / 64 + if i < 16 and (i ~= 13 or c2 < 32) then + return s_char(0xE0+i, 0x80+c2, 0x80+c1) + elseif i >= 16 and i < 0x110 then + local c3 = i % 64 + i = (i - c3) / 64 + return s_char(0xF0+i, 0x80+c3, 0x80+c2, 0x80+c1) + end + end + end + end +end + + local valid_types = { number = true, string = true, @@ -249,7 +284,7 @@ function json.decode(json) if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end seq = seq..ch; end - s = s..s.char(tonumber(seq, 16)); -- FIXME do proper utf-8 + s = s..utf8char(tonumber(seq, 16)); next(); else error("invalid escape sequence in string"); end end -- cgit v1.2.3 From ac8cdca4e113c908589f4ce40bf7d6ac9ad94854 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 6 Apr 2013 20:07:08 +0100 Subject: prosodyctl: Define prosody.core_post_stanza as an empty function --- prosodyctl | 1 + 1 file changed, 1 insertion(+) diff --git a/prosodyctl b/prosodyctl index a8cf0e69..71b99f9e 100755 --- a/prosodyctl +++ b/prosodyctl @@ -51,6 +51,7 @@ local prosody = { lock_globals = function () end; unlock_globals = function () end; installed = CFG_SOURCEDIR ~= nil; + core_post_stanza = function () end; -- TODO: mod_router! }; _G.prosody = prosody; -- cgit v1.2.3 From c35e7caa9fca47fa9daa73a0012cd296cd28b641 Mon Sep 17 00:00:00 2001 From: Marco Cirillo Date: Sun, 7 Apr 2013 12:23:29 +0000 Subject: net.http.server: add API to allow firing events directly on the server. --- net/http/server.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index 20c2da3e..830579c9 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -284,6 +284,9 @@ end function _M.set_default_host(host) default_host = host; end +function _M.fire_event(event, ...) + return events.fire_event(event, ...); +end _M.listener = listener; _M.codes = codes; -- cgit v1.2.3 From 190ca51b563bef31dc671943543bb332b4a48422 Mon Sep 17 00:00:00 2001 From: Marco Cirillo Date: Sun, 7 Apr 2013 16:56:49 +0000 Subject: util.datamanager: expose path decode and encode functions. --- util/datamanager.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/datamanager.lua b/util/datamanager.lua index 383e738f..9f29458c 100644 --- a/util/datamanager.lua +++ b/util/datamanager.lua @@ -354,4 +354,6 @@ function purge(username, host) return #errs == 0, t_concat(errs, ", "); end +_M.path_decode = decode; +_M.path_encode = encode; return _M; -- cgit v1.2.3 From 8e477e369ae77744e0e087f8fb644e370cfc7e25 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 7 Apr 2013 20:28:12 +0100 Subject: util.datamanager: Clear the cache of created directories on storage failure, and retry --- util/datamanager.lua | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/util/datamanager.lua b/util/datamanager.lua index 9f29458c..4a4d62b3 100644 --- a/util/datamanager.lua +++ b/util/datamanager.lua @@ -187,17 +187,25 @@ function store(username, host, datastore, data) -- save the datastore local d = "return " .. serialize(data) .. ";\n"; - local ok, msg = atomic_store(getpath(username, host, datastore, nil, true), 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 nil, "Error saving to storage"; - 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 + 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 -- cgit v1.2.3 From b026f94410817b6927769c5fd410d3e98b326091 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 15:04:55 +0100 Subject: mod_pubsub: Don't attempt to handle iq stanzas with no action element --- plugins/mod_pubsub.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua index 5ff3a029..8f078f54 100644 --- a/plugins/mod_pubsub.lua +++ b/plugins/mod_pubsub.lua @@ -22,6 +22,7 @@ function handle_pubsub_iq(event) local origin, stanza = event.origin, event.stanza; local pubsub = stanza.tags[1]; local action = pubsub.tags[1]; + if not action then return; end local handler = handlers[stanza.attr.type.."_"..action.name]; if handler then handler(origin, stanza, action); -- cgit v1.2.3 From f9c0972b482fe78738477bd4300b74cd274d67e6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 15:32:24 +0100 Subject: mod_pubsub: Send bad-request when no action specified (thanks Maranda) --- plugins/mod_pubsub.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua index 8f078f54..7bd33102 100644 --- a/plugins/mod_pubsub.lua +++ b/plugins/mod_pubsub.lua @@ -22,7 +22,9 @@ function handle_pubsub_iq(event) local origin, stanza = event.origin, event.stanza; local pubsub = stanza.tags[1]; local action = pubsub.tags[1]; - if not action then return; end + if not action then + return origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end local handler = handlers[stanza.attr.type.."_"..action.name]; if handler then handler(origin, stanza, action); -- cgit v1.2.3 From 0191c56238fb5f412a97909acf0b8650f5647436 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 15:53:18 +0100 Subject: sessionmanager, s2smanager: Remove open_session tracing --- core/s2smanager.lua | 9 +-------- core/sessionmanager.lua | 11 +---------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 5777cb8e..e4de498a 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -24,15 +24,8 @@ local fire_event = prosody.events.fire_event; module "s2smanager" -local open_sessions = 0; - function new_incoming(conn) local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} }; - if true then - session.trace = newproxy(true); - getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end; - end - open_sessions = open_sessions + 1; session.log = logger_init("s2sin"..tostring(session):match("[a-f0-9]+$")); incoming_s2s[session] = true; return session; @@ -62,7 +55,7 @@ local resting_session = { -- Resting, not dead function retire_session(session, reason) local log = session.log or log; for k in pairs(session) do - if k ~= "trace" and k ~= "log" and k ~= "id" and k ~= "conn" then + if k ~= "log" and k ~= "id" and k ~= "conn" then session[k] = nil; end end diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index e721835d..d178fb2d 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -29,17 +29,8 @@ local getmetatable = getmetatable; module "sessionmanager" -local open_sessions = 0; - function new_session(conn) local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() }; - if true then - session.trace = newproxy(true); - getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end; - end - open_sessions = open_sessions + 1; - log("debug", "open sessions now: %d", open_sessions); - local filter = initialize_filters(session); local w = conn.write; session.send = function (t) @@ -72,7 +63,7 @@ local resting_session = { -- Resting, not dead function retire_session(session) local log = session.log or log; for k in pairs(session) do - if k ~= "trace" and k ~= "log" and k ~= "id" then + if k ~= "log" and k ~= "id" then session[k] = nil; end end -- cgit v1.2.3 From 7cad1852fd2986eaa374d20d722a49d629717c85 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 16:40:27 +0100 Subject: net.http: Throw error when connecting to a http:// URL without LuaSec available --- net/http.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/http.lua b/net/http.lua index a1e4e523..ec55af92 100644 --- a/net/http.lua +++ b/net/http.lua @@ -11,6 +11,8 @@ local b64 = require "util.encodings".base64.encode; local url = require "socket.url" local httpstream_new = require "util.httpstream".new; +local ssl_available = pcall(require, "ssl"); + local server = require "net.server" local t_insert, t_concat = table.insert, table.concat; @@ -177,6 +179,9 @@ function request(u, ex, callback) req.method, req.headers, req.body = method, headers, body; local using_https = req.scheme == "https"; + if using_https and not ssl_available then + error("SSL not available, unable to contact https URL"); + end local port = tonumber(req.port) or (using_https and 443 or 80); -- Connect the socket, and wrap it with net.server -- cgit v1.2.3 From 41a941b545ed8e23788a23f4f5ad5ef1b7f28a05 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 16:56:40 +0100 Subject: util.pposix: syslog(): Support an optional source parameter (producing messages of the form ': ' --- util-src/pposix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/util-src/pposix.c b/util-src/pposix.c index 99a308cf..c8c25ba9 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -204,12 +204,13 @@ int level_constants[] = { }; int lc_syslog_log(lua_State* L) { - int level = luaL_checkoption(L, 1, "notice", level_strings); - level = level_constants[level]; + int level = level_constants[luaL_checkoption(L, 1, "notice", level_strings)]; - luaL_checkstring(L, 2); + if(lua_gettop(L) == 3) + syslog(level, "%s: %s", luaL_checkstring(L, 2), luaL_checkstring(L, 3)); + else + syslog(level, "%s", lua_tostring(L, 2)); - syslog(level, "%s", lua_tostring(L, 2)); return 0; } -- cgit v1.2.3 From 34582960a73440e2a2cc8a5b77fd86ddc750c950 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 16:57:05 +0100 Subject: mod_posix: Pass logger name to syslog, so that sources now get logged --- plugins/mod_posix.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index e871e5cf..96a05d73 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -118,9 +118,9 @@ function syslog_sink_maker(config) local syslog, format = pposix.syslog_log, string.format; return function (name, level, message, ...) if ... then - syslog(level, format(message, ...)); + syslog(level, name, format(message, ...)); else - syslog(level, message); + syslog(level, name, message); end end; end -- cgit v1.2.3 From 2f0f5d1c7f227a03551be207f231ad8d405b3366 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 16:57:59 +0100 Subject: mod_posix, util.pposix: Bump version for API change --- plugins/mod_posix.lua | 2 +- util-src/pposix.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index 96a05d73..f1957d1d 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -7,7 +7,7 @@ -- -local want_pposix_version = "0.3.5"; +local want_pposix_version = "0.3.6"; local pposix = assert(require "util.pposix"); if pposix._VERSION ~= want_pposix_version then module:log("warn", "Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version); end diff --git a/util-src/pposix.c b/util-src/pposix.c index c8c25ba9..f5cc8270 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -13,7 +13,7 @@ * POSIX support functions for Lua */ -#define MODULE_VERSION "0.3.5" +#define MODULE_VERSION "0.3.6" #include #include -- cgit v1.2.3 From 7c232e291919c829b1f983d808f0b7697739bb68 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Apr 2013 17:21:16 +0100 Subject: mod_posix: Improve error message for a pposix version mismatch --- plugins/mod_posix.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index f1957d1d..28fd7f38 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -10,7 +10,9 @@ local want_pposix_version = "0.3.6"; local pposix = assert(require "util.pposix"); -if pposix._VERSION ~= want_pposix_version then module:log("warn", "Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version); end +if pposix._VERSION ~= want_pposix_version then + module:log("warn", "Unknown version (%s) of binary pposix module, expected %s. Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version); +end local signal = select(2, pcall(require, "util.signal")); if type(signal) == "string" then -- cgit v1.2.3 From bf5ef696c1390a444651681be8c7e0444da55dc2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 8 Apr 2013 22:42:38 +0200 Subject: mod_s2s: Adjust priority of route/remote hooks to negative values (like most other internal hooks) --- plugins/mod_s2s/mod_s2s.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 1547345d..eb10cd35 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -140,8 +140,8 @@ function module.add_host(module) module:log("warn", "The 'disallow_s2s' config option is deprecated, please see http://prosody.im/doc/s2s#disabling"); return nil, "This host has disallow_s2s set"; end - module:hook("route/remote", route_to_existing_session, 200); - module:hook("route/remote", route_to_new_session, 100); + module:hook("route/remote", route_to_existing_session, -1); + module:hook("route/remote", route_to_new_session, -10); module:hook("s2s-authenticated", make_authenticated, -1); end -- cgit v1.2.3 From d78ae8ef3b5e1c8e9467ba79e0e05620b006419f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 17:32:59 +0100 Subject: net.http, util.http: Move definitions of urlencode/decode and formencode/decode to util.http (possible to use them without unnecessary network-related dependencies) --- net/http.lua | 48 +++++++----------------------------------------- util/http.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/net/http.lua b/net/http.lua index ec55af92..516afe58 100644 --- a/net/http.lua +++ b/net/http.lua @@ -10,6 +10,7 @@ local socket = require "socket" local b64 = require "util.encodings".base64.encode; local url = require "socket.url" local httpstream_new = require "util.httpstream".new; +local util_http = require "util.http"; local ssl_available = pcall(require, "ssl"); @@ -70,46 +71,7 @@ function listener.ondisconnect(conn, err) requests[conn] = nil; end -function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); end -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 - -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 - -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 request_reader(request, data, startpos) +local function request_reader(request, data) if not request.parser then if not data then return; end local function success_cb(r) @@ -216,6 +178,10 @@ function destroy_request(request) end end -_M.urlencode = urlencode; +local urlencode, urldecode = util_http.urlencode, util_http.urldecode; +local formencode, formdecode = util_http.formencode, util_http.formdecode; + +_M.urlencode, _M.urldecode = urlencode, urldecode; +_M.formencode, _M.formdecode = formencode, formdecode; return _M; diff --git a/util/http.lua b/util/http.lua index 5b49d1d0..5dd636d9 100644 --- a/util/http.lua +++ b/util/http.lua @@ -7,9 +7,54 @@ local http = {}; +function http.urlencode(s) + return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); +end +function http.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 + +function http.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 + +function http.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 + function http.contains_token(field, token) field = ","..field:gsub("[ \t]", ""):lower()..","; return field:find(","..token:lower()..",", 1, true) ~= nil; end + + return http; -- cgit v1.2.3 From 9c5fa338e56457110512493c868f5adc3474eb7c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 17:35:39 +0100 Subject: sessionmanager, s2smanager: Remove unused imports --- core/s2smanager.lua | 4 ++-- core/sessionmanager.lua | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index e4de498a..06d3f2c9 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -9,8 +9,8 @@ local hosts = prosody.hosts; -local tostring, pairs, getmetatable, newproxy, setmetatable - = tostring, pairs, getmetatable, newproxy, setmetatable; +local tostring, pairs, setmetatable + = tostring, pairs, setmetatable; local logger_init = require "util.logger".init; diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index d178fb2d..98ead07f 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -24,9 +24,6 @@ local uuid_generate = require "util.uuid".generate; local initialize_filters = require "util.filters".initialize; local gettime = require "socket".gettime; -local newproxy = newproxy; -local getmetatable = getmetatable; - module "sessionmanager" function new_session(conn) -- cgit v1.2.3 From 699f10e1fc74059c276b5dd3e7bb21efe5e956ff Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 17:37:37 +0100 Subject: net.http.parser: Depend on util.http instead of net.http for urlencode --- net/http/parser.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 2545b5ac..45a8b168 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -2,7 +2,7 @@ local tonumber = tonumber; local assert = assert; local url_parse = require "socket.url".parse; -local urldecode = require "net.http".urldecode; +local urldecode = require "util.http".urldecode; local function preprocess_path(path) path = urldecode((path:gsub("//+", "/"))); -- cgit v1.2.3 From 6524112e6cf10de1e0678ae0edcc66d891c34a92 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 17:39:10 +0100 Subject: net.http.parser: Break when no more usable data in buffer (client part of e5ec60dfb202) --- net/http/parser.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/http/parser.lua b/net/http/parser.lua index 45a8b168..73a8fb6a 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -136,6 +136,8 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) elseif len and #buf >= len then packet.body, buf = buf:sub(1, len), buf:sub(len + 1); state = nil; success_cb(packet); + else + break; end elseif #buf >= len then packet.body, buf = buf:sub(1, len), buf:sub(len + 1); -- cgit v1.2.3 From 8e40a3b37734c2ce7cbae5bfbf858a1f787492f2 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 19:58:53 +0100 Subject: net.http.parser: Convert status_code to a number before trying to compare it to numbers --- net/http/parser.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/parser.lua b/net/http/parser.lua index 73a8fb6a..684d62fe 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -65,6 +65,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) first_line = line; if client then httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); + status_code = tonumber(status_code); if not status_code then error = true; return error_cb("invalid-status-line"); end have_body = not ( (options_cb and options_cb().method == "HEAD") -- cgit v1.2.3 From bbcf8d50b310288ae015c17d115eb92ca96bdb3b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 20:01:03 +0100 Subject: net.http.parser: Fix chunked encoding response parsing, and make it more robust --- net/http/parser.lua | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 684d62fe..34742d2b 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -1,4 +1,3 @@ - local tonumber = tonumber; local assert = assert; local url_parse = require "socket.url".parse; @@ -29,7 +28,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) local client = true; if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end local buf = ""; - local chunked; + local chunked, chunk_size, chunk_start; local state = nil; local packet; local len; @@ -71,7 +70,6 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) ( (options_cb and options_cb().method == "HEAD") or (status_code == 204 or status_code == 304 or status_code == 301) or (status_code >= 100 and status_code < 200) ); - chunked = have_body and headers["transfer-encoding"] == "chunked"; else method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); if not method then error = true; return error_cb("invalid-status-line"); end @@ -79,6 +77,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) end end if not first_line then error = true; return error_cb("invalid-status-line"); end + chunked = have_body and headers["transfer-encoding"] == "chunked"; len = tonumber(headers["content-length"]); -- TODO check for invalid len if client then -- FIXME handle '100 Continue' response (by skipping it) @@ -121,19 +120,25 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) if state then -- read body if client then if chunked then - local index = buf:find("\r\n", nil, true); - if not index then return; end -- not enough data - local chunk_size = buf:match("^%x+"); - if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end - chunk_size = tonumber(chunk_size, 16); - index = index + 2; - if chunk_size == 0 then - state = nil; success_cb(packet); - elseif #buf - index + 1 >= chunk_size then -- we have a chunk - packet.body = packet.body..buf:sub(index, index + chunk_size - 1); - buf = buf:sub(index + chunk_size); + if not buf:find("\r\n", nil, true) then + return; + end -- not enough data + if not chunk_size then + chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); + chunk_size = chunk_size and tonumber(chunk_size, 16); + if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end + end + if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then + state, chunk_size = nil, nil; + buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped + success_cb(packet); + elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk + packet.body = packet.body..buf:sub(chunk_start, chunk_start + chunk_size); + buf = buf:sub(chunk_start + chunk_size + 2); + chunk_size, chunk_start = nil, nil; + else -- Partial chunk remaining + break; end - error("trailers"); -- FIXME MUST read trailers elseif len and #buf >= len then packet.body, buf = buf:sub(1, len), buf:sub(len + 1); state = nil; success_cb(packet); -- cgit v1.2.3 From d8ddce487688bf5cc99d0e4654eb5b8ece2f4c34 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 20:24:37 +0100 Subject: net.http: Switch from util.httpstream to net.http.parser, introduces small but backwards-incompatible API changes - see http://prosody.im/doc/developers/http --- net/http.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http.lua b/net/http.lua index 516afe58..b3bd5a67 100644 --- a/net/http.lua +++ b/net/http.lua @@ -9,7 +9,7 @@ local socket = require "socket" local b64 = require "util.encodings".base64.encode; local url = require "socket.url" -local httpstream_new = require "util.httpstream".new; +local httpstream_new = require "net.http.parser".new; local util_http = require "util.http"; local ssl_available = pcall(require, "ssl"); -- cgit v1.2.3 From f663e8b2df6930a54861f681bed9d639393686ac Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 11 Apr 2013 21:55:20 +0100 Subject: util.httpstream: Unused, remove --- util/httpstream.lua | 134 ---------------------------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 util/httpstream.lua diff --git a/util/httpstream.lua b/util/httpstream.lua deleted file mode 100644 index 190b3ed6..00000000 --- a/util/httpstream.lua +++ /dev/null @@ -1,134 +0,0 @@ - -local coroutine = coroutine; -local tonumber = tonumber; - -local deadroutine = coroutine.create(function() end); -coroutine.resume(deadroutine); - -module("httpstream") - -local function parser(success_cb, parser_type, options_cb) - local data = coroutine.yield(); - local function readline() - local pos = data:find("\r\n", nil, true); - while not pos do - data = data..coroutine.yield(); - pos = data:find("\r\n", nil, true); - end - local r = data:sub(1, pos-1); - data = data:sub(pos+2); - return r; - end - local function readlength(n) - while #data < n do - data = data..coroutine.yield(); - end - local r = data:sub(1, n); - data = data:sub(n + 1); - return r; - end - local function readheaders() - local headers = {}; -- read headers - while true do - local line = readline(); - if line == "" then break; end -- headers done - local key, val = line:match("^([^%s:]+): *(.*)$"); - if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers - key = key:lower(); - headers[key] = headers[key] and headers[key]..","..val or val; - end - return headers; - end - - if not parser_type or parser_type == "server" then - while true do - -- read status line - local status_line = readline(); - local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$"); - if not method then coroutine.yield("invalid-status-line"); end - path = path:gsub("^//+", "/"); -- TODO parse url more - local headers = readheaders(); - - -- read body - local len = tonumber(headers["content-length"]); - len = len or 0; -- TODO check for invalid len - local body = readlength(len); - - success_cb({ - method = method; - path = path; - httpversion = httpversion; - headers = headers; - body = body; - }); - end - elseif parser_type == "client" then - while true do - -- read status line - local status_line = readline(); - local httpversion, status_code, reason_phrase = status_line:match("^HTTP/(%S+)%s+(%d%d%d)%s+(.*)$"); - status_code = tonumber(status_code); - if not status_code then coroutine.yield("invalid-status-line"); end - local headers = readheaders(); - - -- read body - local have_body = not - ( (options_cb and options_cb().method == "HEAD") - or (status_code == 204 or status_code == 304 or status_code == 301) - or (status_code >= 100 and status_code < 200) ); - - local body; - if have_body then - local len = tonumber(headers["content-length"]); - if headers["transfer-encoding"] == "chunked" then - body = ""; - while true do - local chunk_size = readline():match("^%x+"); - if not chunk_size then coroutine.yield("invalid-chunk-size"); end - chunk_size = tonumber(chunk_size, 16) - if chunk_size == 0 then break; end - body = body..readlength(chunk_size); - if readline() ~= "" then coroutine.yield("invalid-chunk-ending"); end - end - local trailers = readheaders(); - elseif len then -- TODO check for invalid len - body = readlength(len); - else -- read to end - repeat - local newdata = coroutine.yield(); - data = data..newdata; - until newdata == ""; - body, data = data, ""; - end - end - - success_cb({ - code = status_code; - httpversion = httpversion; - headers = headers; - body = body; - }); - end - else coroutine.yield("unknown-parser-type"); end -end - -function new(success_cb, error_cb, parser_type, options_cb) - local co = coroutine.create(parser); - coroutine.resume(co, success_cb, parser_type, options_cb) - return { - feed = function(self, data) - if not data then - if parser_type == "client" then coroutine.resume(co, ""); end - co = deadroutine; - return error_cb(); - end - local success, result = coroutine.resume(co, data); - if result then - co = deadroutine; - return error_cb(result); - end - end; - }; -end - -return _M; -- cgit v1.2.3 From 2853b6385d419357b3429ab146fda8ff6f7133cc Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Apr 2013 00:31:05 +0100 Subject: net.http: Swap response and request parameters passed to callback (will break some modules) --- net/http.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/http.lua b/net/http.lua index b3bd5a67..4eb4a2ac 100644 --- a/net/http.lua +++ b/net/http.lua @@ -76,8 +76,7 @@ local function request_reader(request, data) if not data then return; end local function success_cb(r) if request.callback then - for k,v in pairs(r) do request[k] = v; end - request.callback(r.body, r.code, request, r); + request.callback(r.body, r.code, r, request); request.callback = nil; end destroy_request(request); -- cgit v1.2.3 From 733476db7b1b6094cc60196cd6f8cdc633e5694a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Apr 2013 00:44:49 +0100 Subject: util.iterators: Add ripairs() (ipairs() in reverse) (thanks Maranda) --- util/iterators.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/iterators.lua b/util/iterators.lua index fb89f4a5..1f6aacb8 100644 --- a/util/iterators.lua +++ b/util/iterators.lua @@ -122,6 +122,11 @@ function it.tail(n, f, s, var) --return reverse(head(n, reverse(f, 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 -- cgit v1.2.3 From 422f54a5c60227e5096a599c7a77e4263bea18d8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 9 Apr 2013 15:50:46 +0200 Subject: prosodyctl: Bump util.pposix version for API change --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 71b99f9e..24d28157 100755 --- a/prosodyctl +++ b/prosodyctl @@ -135,7 +135,7 @@ dependencies.log_warnings(); -- Switch away from root and into the prosody user -- local switched_user, current_uid; -local want_pposix_version = "0.3.5"; +local want_pposix_version = "0.3.6"; local ok, pposix = pcall(require, "util.pposix"); if ok and pposix then -- cgit v1.2.3 From d61990f3fd8e6ab4b58e6c5af477d6430955e622 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Apr 2013 20:26:35 +0100 Subject: util.http: Refactor and import all necessary functions --- util/http.lua | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/util/http.lua b/util/http.lua index 5dd636d9..f7259920 100644 --- a/util/http.lua +++ b/util/http.lua @@ -5,12 +5,14 @@ -- COPYING file in the source package for more information. -- -local http = {}; +local format, char = string.format, string.char; +local pairs, ipairs, tonumber = pairs, ipairs, tonumber; +local t_insert, t_concat = table.insert, table.concat; -function http.urlencode(s) +local function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); end -function http.urldecode(s) +local function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end @@ -24,7 +26,7 @@ local function _formencodepart(s) end)); end -function http.formencode(form) +local function formencode(form) local result = {}; if form[1] then -- Array of ordered { name, value } for _, field in ipairs(form) do @@ -38,7 +40,7 @@ function http.formencode(form) return t_concat(result, "&"); end -function http.formdecode(s) +local function formdecode(s) if not s:match("=") then return urldecode(s); end local r = {}; for k, v in s:gmatch("([^=&]*)=([^&]*)") do @@ -50,11 +52,13 @@ function http.formdecode(s) return r; end -function http.contains_token(field, token) +local function contains_token(field, token) field = ","..field:gsub("[ \t]", ""):lower()..","; return field:find(","..token:lower()..",", 1, true) ~= nil; end - - -return http; +return { + urlencode = urlencode, urldecode = urldecode; + formencode = formencode, formdecode = formdecode; + contains_token = contains_token; +}; -- cgit v1.2.3 From e8bee5c5bae052c8e22065b6212f7ee04193bde1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Apr 2013 12:30:40 +0100 Subject: Makefile: Specify explicit mode when installing prosody.version, to avoid it defaulting to something nasty (executable) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0e69192a..b96b6732 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin install -m644 certs/* $(CONFIG)/certs install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1 test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua - test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true + test -e prosody.version && install -m644 prosody.version $(SOURCE)/prosody.version || true $(MAKE) install -C util-src clean: -- cgit v1.2.3 From df24c2b55ccd46351158a94ab1d8700f17d74f5a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 15 Apr 2013 19:37:15 +0200 Subject: mod_s2s: Add missing space --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index eb10cd35..6764e857 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -642,7 +642,7 @@ function check_auth_policy(event) if must_secure and not session.cert_identity_status then module:log("warn", "Forbidding insecure connection to/from %s", host); if session.direction == "incoming" then - session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by"..session.to_host }); + session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by "..session.to_host }); else -- Close outgoing connections without warning session:close(false); end -- cgit v1.2.3 From d3ae92484963e2a650e4ed5d87fe8c19970bbd2a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Apr 2013 21:21:57 +0100 Subject: net.http.parser: Fix off-by-one error in chunked encoding parser --- net/http/parser.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 34742d2b..9688e052 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -133,7 +133,8 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped success_cb(packet); elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk - packet.body = packet.body..buf:sub(chunk_start, chunk_start + chunk_size); + print(chunk_start, chunk_size, ("%q"):format(buf)) + packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); buf = buf:sub(chunk_start + chunk_size + 2); chunk_size, chunk_start = nil, nil; else -- Partial chunk remaining -- cgit v1.2.3 From 7d170b2286cbc6b1491ffdf128bace416a86c5e9 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Apr 2013 21:25:59 +0100 Subject: net.http.parser: Remove accidentally-committed debugging --- net/http/parser.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 9688e052..f9e6cea0 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -133,7 +133,6 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped success_cb(packet); elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk - print(chunk_start, chunk_size, ("%q"):format(buf)) packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); buf = buf:sub(chunk_start + chunk_size + 2); chunk_size, chunk_start = nil, nil; -- cgit v1.2.3 From 66bd53b9d22135237afb974a8d2d2a665777379b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 17 Apr 2013 21:30:44 +0200 Subject: net.server_select: Normalize indentation --- net/server_select.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index f123f4b7..6592de9b 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -569,7 +569,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport end out_put( "server.lua: ssl handshake error: ", tostring(err or "handshake too long") ) _ = handler and handler:force_close("ssl handshake failed") - return false, err -- handshake failed + return false, err -- handshake failed end ) end @@ -613,7 +613,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport handler.readbuffer = handshake handler.sendbuffer = handshake - return handshake( socket ) -- do handshake + return handshake( socket ) -- do handshake end end @@ -629,10 +629,10 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport if sslctx and luasec then out_put "server.lua: auto-starting ssl negotiation..." handler.autostart_ssl = true; - local ok, err = handler:starttls(sslctx); - if ok == false then - return nil, nil, err - end + local ok, err = handler:starttls(sslctx); + if ok == false then + return nil, nil, err + end end return handler, socket -- cgit v1.2.3 From 65d488d97548f04cdece2495f5f8c84f154f0073 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Tue, 16 Apr 2013 18:15:10 -0400 Subject: net.server_select: Move socket timeout cleanup code out of a timer, into the select loop (which makes util.timer the only timer using server_select._addtimer). --- net/server_select.lua | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 6592de9b..86c9daef 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -846,6 +846,26 @@ loop = function(once) -- this is the main loop of the program _closelist[ handler ] = nil; end _currenttime = luasocket_gettime( ) + + local difftime = os_difftime( _currenttime - _starttime ) + if difftime > _checkinterval then + _starttime = _currenttime + for handler, timestamp in pairs( _writetimes ) do + if os_difftime( _currenttime - timestamp ) > _sendtimeout then + --_writetimes[ handler ] = nil + handler.disconnect( )( handler, "send timeout" ) + handler:force_close() -- forced disconnect + end + end + for handler, timestamp in pairs( _readtimes ) do + if os_difftime( _currenttime - timestamp ) > _readtimeout then + --_readtimes[ handler ] = nil + handler.disconnect( )( handler, "read timeout" ) + handler:close( ) -- forced disconnect? + end + end + end + if _currenttime - _timer >= math_min(next_timer_time, 1) then next_timer_time = math_huge; for i = 1, _timerlistlen do @@ -921,28 +941,6 @@ use "setmetatable" ( _writetimes, { __mode = "k" } ) _timer = luasocket_gettime( ) _starttime = luasocket_gettime( ) -addtimer( function( ) - local difftime = os_difftime( _currenttime - _starttime ) - if difftime > _checkinterval then - _starttime = _currenttime - for handler, timestamp in pairs( _writetimes ) do - if os_difftime( _currenttime - timestamp ) > _sendtimeout then - --_writetimes[ handler ] = nil - handler.disconnect( )( handler, "send timeout" ) - handler:force_close() -- forced disconnect - end - end - for handler, timestamp in pairs( _readtimes ) do - if os_difftime( _currenttime - timestamp ) > _readtimeout then - --_readtimes[ handler ] = nil - handler.disconnect( )( handler, "read timeout" ) - handler:close( ) -- forced disconnect? - end - end - end - end -) - local function setlogger(new_logger) local old_logger = log; if new_logger then -- cgit v1.2.3 From f76ad9a7f360ddb10e694bfffccd3b5388e87eca Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Tue, 16 Apr 2013 18:18:22 -0400 Subject: net.server_select: Add and improve some comments. --- net/server_select.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 86c9daef..983b06a2 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -847,6 +847,7 @@ loop = function(once) -- this is the main loop of the program end _currenttime = luasocket_gettime( ) + -- Check for socket timeouts local difftime = os_difftime( _currenttime - _starttime ) if difftime > _checkinterval then _starttime = _currenttime @@ -866,6 +867,7 @@ loop = function(once) -- this is the main loop of the program end end + -- Fire timers if _currenttime - _timer >= math_min(next_timer_time, 1) then next_timer_time = math_huge; for i = 1, _timerlistlen do @@ -876,8 +878,9 @@ loop = function(once) -- this is the main loop of the program else next_timer_time = next_timer_time - (_currenttime - _timer); end - socket_sleep( _sleeptime ) -- wait some time - --collectgarbage( ) + + -- wait some time (0 by default) + socket_sleep( _sleeptime ) until quitting; if once and quitting == "once" then quitting = nil; return; end return "quitting" -- cgit v1.2.3 From e6dddcc8ecaee293bf1354b84e7aa5380a1b8625 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 17 Apr 2013 14:12:47 +0100 Subject: mod_pubsub: Add id to stored item when auto-generated. Fixes #335 --- plugins/mod_pubsub.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua index 7bd33102..4d3911bb 100644 --- a/plugins/mod_pubsub.lua +++ b/plugins/mod_pubsub.lua @@ -190,7 +190,11 @@ function handlers.set_publish(origin, stanza, publish) return origin.send(pubsub_error_reply(stanza, "nodeid-required")); end local item = publish:get_child("item"); - local id = (item and item.attr.id) or uuid_generate(); + local id = (item and item.attr.id); + if not id then + id = uuid_generate(); + item.attr.id = id; + end local ok, ret = service:publish(node, stanza.attr.from, id, item); local reply; if ok then -- cgit v1.2.3 From edef039dcac37cddbb4612e426fa22c3cbf6313c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 17 Apr 2013 14:32:26 +0100 Subject: mod_pubsub: Only assign id to item element if there is one --- plugins/mod_pubsub.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua index 4d3911bb..22969ab5 100644 --- a/plugins/mod_pubsub.lua +++ b/plugins/mod_pubsub.lua @@ -193,7 +193,9 @@ function handlers.set_publish(origin, stanza, publish) local id = (item and item.attr.id); if not id then id = uuid_generate(); - item.attr.id = id; + if item then + item.attr.id = id; + end end local ok, ret = service:publish(node, stanza.attr.from, id, item); local reply; -- cgit v1.2.3 From 8f1b950b8da2d5632d463b7210cc2c2dc9586f03 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 17 Apr 2013 19:10:04 +0200 Subject: net.server_select: Don't call onconnect twice on SSL connections --- net/server_select.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 8ce9eed2..f123f4b7 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -551,9 +551,6 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport handler.readbuffer = _readbuffer -- when handshake is done, replace the handshake function with regular functions handler.sendbuffer = _sendbuffer _ = status and status( handler, "ssl-handshake-complete" ) - if self.autostart_ssl and listeners.onconnect then - listeners.onconnect(self); - end _readlistlen = addsocket(_readlist, client, _readlistlen) return true else -- cgit v1.2.3 From eb6601038d104544eb034e3846344d2ba833d82c Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 17 Apr 2013 15:28:20 -0400 Subject: net.http.server: The correct Connection header value to look for is Keep-Alive, not keep-alive. --- net/http/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/server.lua b/net/http/server.lua index 830579c9..a983b8d5 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -159,7 +159,7 @@ function handle_request(conn, request, finish_cb) local conn_header = request.headers.connection; conn_header = conn_header and ","..conn_header:gsub("[ \t]", ""):lower().."," or "" local httpversion = request.httpversion - local persistent = conn_header:find(",keep-alive,", 1, true) + local persistent = conn_header:find(",Keep-Alive,", 1, true) or (httpversion == "1.1" and not conn_header:find(",close,", 1, true)); local response_conn_header; -- cgit v1.2.3 From fc2f7c817af24bcb2c1d978120f81825b2d986a2 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 00:08:58 +0100 Subject: net.http: When HTTP request fails due to a network or SSL error, call the callback to let it know --- net/http.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/net/http.lua b/net/http.lua index 4eb4a2ac..639ecf6a 100644 --- a/net/http.lua +++ b/net/http.lua @@ -66,24 +66,29 @@ end function listener.ondisconnect(conn, err) local request = requests[conn]; if request and request.conn then - request:reader(nil); + request:reader(nil, err); end requests[conn] = nil; end -local function request_reader(request, data) +local function request_reader(request, data, err) if not request.parser then - if not data then return; end - local function success_cb(r) + local function error_cb(reason) if request.callback then - request.callback(r.body, r.code, r, request); + request.callback(reason or "connection-closed", 0, request); request.callback = nil; end destroy_request(request); end - local function error_cb(r) + + if not data then + error_cb(err); + return; + end + + local function success_cb(r) if request.callback then - request.callback(r or "connection-closed", 0, request); + request.callback(r.body, r.code, r, request); request.callback = nil; end destroy_request(request); -- cgit v1.2.3 From 9544158f980bf2a08b0e5f438bd0eff22f53996a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 00:39:59 +0100 Subject: Backed out changeset f2631a14b953 --- net/server_select.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/server_select.lua b/net/server_select.lua index f123f4b7..8ce9eed2 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -551,6 +551,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport handler.readbuffer = _readbuffer -- when handshake is done, replace the handshake function with regular functions handler.sendbuffer = _sendbuffer _ = status and status( handler, "ssl-handshake-complete" ) + if self.autostart_ssl and listeners.onconnect then + listeners.onconnect(self); + end _readlistlen = addsocket(_readlist, client, _readlistlen) return true else -- cgit v1.2.3 From 56116d1a7c7feb5ca7173d98506fab13fe3ad602 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 00:41:03 +0100 Subject: net.server_select: Don't call onconnect twice for SSL connections --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index ca80534c..28f1dc6d 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -243,7 +243,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- t end connections = connections + 1 out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) - if dispatch then + if dispatch and not sslctx then -- SSL connections will notify onconnect when handshake completes return dispatch( handler ); end return; -- cgit v1.2.3 From d5d5ac230db5b83a3c45381a1dc5ab66d0cfc53b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 00:41:03 +0100 Subject: net.server_select: Don't call onconnect twice for SSL connections --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index ca80534c..28f1dc6d 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -243,7 +243,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- t end connections = connections + 1 out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) - if dispatch then + if dispatch and not sslctx then -- SSL connections will notify onconnect when handshake completes return dispatch( handler ); end return; -- cgit v1.2.3 From a2cf7ac6a49ad85eaf290d51f7ba3c0a8385b76a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 11:13:40 +0100 Subject: mod_storage_sql2 (temporary name), sql.lib, util.sql: New SQL API supporting cross-module connection sharing, transactions and Things - a work in progress --- plugins/mod_storage_sql2.lua | 237 ++++++++++++++++++++++++++++++ plugins/sql.lib.lua | 9 ++ util/sql.lua | 340 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 586 insertions(+) create mode 100644 plugins/mod_storage_sql2.lua create mode 100644 plugins/sql.lib.lua create mode 100644 util/sql.lua diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua new file mode 100644 index 00000000..7d705b0b --- /dev/null +++ b/plugins/mod_storage_sql2.lua @@ -0,0 +1,237 @@ + +local json = require "util.json"; +local resolve_relative_path = require "core.configmanager".resolve_relative_path; + +local mod_sql = module:require("sql"); +local params = module:get_option("sql"); + +local engine; -- TODO create engine + +local function create_table() + --[[local Table,Column,Index = mod_sql.Table,mod_sql.Column,mod_sql.Index; + local ProsodyTable = 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" }; + }; + engine:transaction(function() + ProsodyTable:create(engine); + end);]] + if not module:get_option("sql_manage_tables", true) then + return; + end + + local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);"; + if params.driver == "PostgreSQL" then + create_sql = create_sql:gsub("`", "\""); + elseif params.driver == "MySQL" then + create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT") + :gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';"); + end + + local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)"; + if params.driver == "PostgreSQL" then + index_sql = index_sql:gsub("`", "\""); + elseif params.driver == "MySQL" then + index_sql = index_sql:gsub("`([,)])", "`(20)%1"); + end + + local success,err = engine:transaction(function() + engine:execute(create_sql); + engine:execute(index_sql); + end); + if not success then -- so we failed to create + if params.driver == "MySQL" then + success,err = engine:transaction(function() + local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); + if result:rowcount() > 0 then + module:log("info", "Upgrading database schema..."); + engine:execute("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT"); + module:log("info", "Database table automatically upgraded"); + end + return true; + end); + if not success then + module:log("error", "Failed to check/upgrade database schema (%s), please see " + .."http://prosody.im/doc/mysql for help", + err or "unknown error"); + end + end + end +end +local function set_encoding() + if params.driver ~= "SQLite3" then + local set_names_query = "SET NAMES 'utf8';"; + if params.driver == "MySQL" then + set_names_query = set_names_query:gsub(";$", " COLLATE 'utf8_bin';"); + end + local success,err = engine:transaction(function() return engine:execute(set_names_query); end); + if not success then + module:log("error", "Failed to set database connection encoding to UTF8: %s", err); + return; + end + if params.driver == "MySQL" then + -- COMPAT w/pre-0.9: Upgrade tables to UTF-8 if not already + local check_encoding_query = "SELECT `COLUMN_NAME`,`COLUMN_TYPE` FROM `information_schema`.`columns` WHERE `TABLE_NAME`='prosody' AND ( `CHARACTER_SET_NAME`!='utf8' OR `COLLATION_NAME`!='utf8_bin' );"; + local success,err = engine:transaction(function() + local result = engine:execute(check_encoding_query); + local n_bad_columns = result:rowcount(); + if n_bad_columns > 0 then + module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns); + local fix_column_query1 = "ALTER TABLE `prosody` CHANGE `%s` `%s` BLOB;"; + local fix_column_query2 = "ALTER TABLE `prosody` CHANGE `%s` `%s` %s CHARACTER SET 'utf8' COLLATE 'utf8_bin';"; + for row in success:rows() do + local column_name, column_type = unpack(row); + engine:execute(fix_column_query1:format(column_name, column_name)); + engine:execute(fix_column_query2:format(column_name, column_name, column_type)); + end + module:log("info", "Database encoding upgrade complete!"); + end + end); + local success,err = engine:transaction(function() return engine:execute(check_encoding_query); end); + if not success then + module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error"); + end + end + end +end + +do -- process options to get a db connection + params = params or { driver = "SQLite3" }; + + if params.driver == "SQLite3" then + params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); + end + + assert(params.driver and params.database, "Both the SQL driver and the database need to be specified"); + + --local dburi = db2uri(params); + engine = mod_sql:create_engine(params); + + -- Encoding mess + set_encoding(); + + -- Automatically create table, ignore failure (table probably already exists) + create_table(); +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 host = module.host; +local user, store; + +local function keyval_store_get() + local haveany; + local result = {}; + for row in engine:select("SELECT `key`,`type`,`value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store) do + haveany = true; + local k = row[1]; + local v = deserialize(row[2], row[3]); + 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 + end + if haveany then + return result; + end +end +local function keyval_store_set(data) + engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store); + + if data and next(data) ~= nil then + local extradata = {}; + for key, value in pairs(data) do + if type(key) == "string" and key ~= "" then + local t, value = serialize(value); + assert(t, value); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, key, t, value); + else + extradata[key] = value; + end + end + if next(extradata) ~= nil then + local t, extradata = serialize(extradata); + assert(t, extradata); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, "", t, extradata); + end + end + return true; +end + +local keyval_store = {}; +keyval_store.__index = keyval_store; +function keyval_store:get(username) + user,store = username,self.store; + return select(2, engine:transaction(keyval_store_get)); +end +function keyval_store:set(username, data) + user,store = username,self.store; + return engine:transaction(function() + return keyval_store_set(data); + end); +end +function keyval_store:users() + return engine:transaction(function() + return engine:select("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store); + end); +end + +local driver = {}; + +function driver:open(store, typ) + if not typ then -- default key-value store + return setmetatable({ store = store }, keyval_store); + end + return nil, "unsupported-store"; +end + +function driver:stores(username) + local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" .. + (username == true and "!=?" or "=?"); + if username == true or not username then + username = ""; + end + return engine:transaction(function() + return engine:select(sql, host, username); + end); +end + +function driver:purge(username) + return engine:transaction(function() + local stmt,err = engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username); + return true,err; + end); +end + +module:provides("storage", driver); + + diff --git a/plugins/sql.lib.lua b/plugins/sql.lib.lua new file mode 100644 index 00000000..005ee45d --- /dev/null +++ b/plugins/sql.lib.lua @@ -0,0 +1,9 @@ +local cache = module:shared("/*/sql.lib/util.sql"); + +if not cache._M then + prosody.unlock_globals(); + cache._M = require "util.sql"; + prosody.lock_globals(); +end + +return cache._M; diff --git a/util/sql.lua b/util/sql.lua new file mode 100644 index 00000000..f360d6d0 --- /dev/null +++ b/util/sql.lua @@ -0,0 +1,340 @@ + +local setmetatable, getmetatable = setmetatable, getmetatable; +local ipairs, unpack, select = ipairs, unpack, select; +local tonumber, tostring = tonumber, tostring; +local assert, xpcall, debug_traceback = assert, 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; + +module("sql") + +local column_mt = {}; +local table_mt = {}; +local query_mt = {}; +--local op_mt = {}; +local index_mt = {}; + +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 +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 +function Index(definition) + return setmetatable(definition, index_mt); +end + +function table_mt:__tostring() + local s = { 'name="'..self.__table__.name..'"' } + for i,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 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") + local dbh, err = DBI.Connect( + params.driver, params.database, + params.username, params.password, + params.host, params.port + ); + if not dbh then return nil, err; end + dbh:autocommit(false); -- don't commit automatically + self.conn = dbh; + self.prepared = {}; + return true; +end +function engine:execute(sql, ...) + local success, err = self:connect(); + if not success then return success, err; end + local prepared = self.prepared; + + 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.__affected; end; + rowcount = function(self) return self.__rowcount; end; +} }; + +function engine:execute_query(sql, ...) + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + local stmt = assert(self.conn:prepare(sql)); + assert(stmt:execute(...)); + return stmt:rows(); +end +function engine:execute_update(sql, ...) + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + 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({ __affected = stmt:affected(), __rowcount = stmt:rowcount() }, result_mt); +end +engine.insert = engine.execute_update; +engine.select = engine.execute_query; +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 + 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 + self.__transaction = true; + local success, a, b, c = xpcall(f, debug_traceback); + 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 a,b = self:_transaction(...); + if not a then + local conn = self.conn; + if not conn or not conn:ping() then + self.conn = nil; + a,b = self:_transaction(...); + end + end + return a,b; +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 == "PostgreSQL" then + sql = sql:gsub("`", "\""); + elseif self.params.driver == "MySQL" then + sql = sql:gsub("`([,)])", "`(20)%1"); + 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; + if col.nullable == false then sql = sql.." NOT NULL"; end + if i ~= #table.c then sql = sql..", "; end + end + sql = sql.. ");" + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + local success,err = self:execute(sql); + if not success then return success,err; end + for i,v in ipairs(table.__table__) do + if is_index(v) then + self:_create_index(v); + end + end + return success; +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 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']) +end +--result.close();]] + +return _M; -- cgit v1.2.3 From b06f38170afcf5801bf38add8f141e624c613a82 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Apr 2013 11:13:40 +0100 Subject: mod_storage_sql2 (temporary name), sql.lib, util.sql: New SQL API supporting cross-module connection sharing, transactions and Things - a work in progress --- plugins/mod_storage_sql2.lua | 237 ++++++++++++++++++++++++++++++ plugins/sql.lib.lua | 9 ++ util/sql.lua | 340 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 586 insertions(+) create mode 100644 plugins/mod_storage_sql2.lua create mode 100644 plugins/sql.lib.lua create mode 100644 util/sql.lua diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua new file mode 100644 index 00000000..7d705b0b --- /dev/null +++ b/plugins/mod_storage_sql2.lua @@ -0,0 +1,237 @@ + +local json = require "util.json"; +local resolve_relative_path = require "core.configmanager".resolve_relative_path; + +local mod_sql = module:require("sql"); +local params = module:get_option("sql"); + +local engine; -- TODO create engine + +local function create_table() + --[[local Table,Column,Index = mod_sql.Table,mod_sql.Column,mod_sql.Index; + local ProsodyTable = 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" }; + }; + engine:transaction(function() + ProsodyTable:create(engine); + end);]] + if not module:get_option("sql_manage_tables", true) then + return; + end + + local create_sql = "CREATE TABLE `prosody` (`host` TEXT, `user` TEXT, `store` TEXT, `key` TEXT, `type` TEXT, `value` TEXT);"; + if params.driver == "PostgreSQL" then + create_sql = create_sql:gsub("`", "\""); + elseif params.driver == "MySQL" then + create_sql = create_sql:gsub("`value` TEXT", "`value` MEDIUMTEXT") + :gsub(";$", " CHARACTER SET 'utf8' COLLATE 'utf8_bin';"); + end + + local index_sql = "CREATE INDEX `prosody_index` ON `prosody` (`host`, `user`, `store`, `key`)"; + if params.driver == "PostgreSQL" then + index_sql = index_sql:gsub("`", "\""); + elseif params.driver == "MySQL" then + index_sql = index_sql:gsub("`([,)])", "`(20)%1"); + end + + local success,err = engine:transaction(function() + engine:execute(create_sql); + engine:execute(index_sql); + end); + if not success then -- so we failed to create + if params.driver == "MySQL" then + success,err = engine:transaction(function() + local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); + if result:rowcount() > 0 then + module:log("info", "Upgrading database schema..."); + engine:execute("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT"); + module:log("info", "Database table automatically upgraded"); + end + return true; + end); + if not success then + module:log("error", "Failed to check/upgrade database schema (%s), please see " + .."http://prosody.im/doc/mysql for help", + err or "unknown error"); + end + end + end +end +local function set_encoding() + if params.driver ~= "SQLite3" then + local set_names_query = "SET NAMES 'utf8';"; + if params.driver == "MySQL" then + set_names_query = set_names_query:gsub(";$", " COLLATE 'utf8_bin';"); + end + local success,err = engine:transaction(function() return engine:execute(set_names_query); end); + if not success then + module:log("error", "Failed to set database connection encoding to UTF8: %s", err); + return; + end + if params.driver == "MySQL" then + -- COMPAT w/pre-0.9: Upgrade tables to UTF-8 if not already + local check_encoding_query = "SELECT `COLUMN_NAME`,`COLUMN_TYPE` FROM `information_schema`.`columns` WHERE `TABLE_NAME`='prosody' AND ( `CHARACTER_SET_NAME`!='utf8' OR `COLLATION_NAME`!='utf8_bin' );"; + local success,err = engine:transaction(function() + local result = engine:execute(check_encoding_query); + local n_bad_columns = result:rowcount(); + if n_bad_columns > 0 then + module:log("warn", "Found %d columns in prosody table requiring encoding change, updating now...", n_bad_columns); + local fix_column_query1 = "ALTER TABLE `prosody` CHANGE `%s` `%s` BLOB;"; + local fix_column_query2 = "ALTER TABLE `prosody` CHANGE `%s` `%s` %s CHARACTER SET 'utf8' COLLATE 'utf8_bin';"; + for row in success:rows() do + local column_name, column_type = unpack(row); + engine:execute(fix_column_query1:format(column_name, column_name)); + engine:execute(fix_column_query2:format(column_name, column_name, column_type)); + end + module:log("info", "Database encoding upgrade complete!"); + end + end); + local success,err = engine:transaction(function() return engine:execute(check_encoding_query); end); + if not success then + module:log("error", "Failed to check/upgrade database encoding: %s", err or "unknown error"); + end + end + end +end + +do -- process options to get a db connection + params = params or { driver = "SQLite3" }; + + if params.driver == "SQLite3" then + params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); + end + + assert(params.driver and params.database, "Both the SQL driver and the database need to be specified"); + + --local dburi = db2uri(params); + engine = mod_sql:create_engine(params); + + -- Encoding mess + set_encoding(); + + -- Automatically create table, ignore failure (table probably already exists) + create_table(); +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 host = module.host; +local user, store; + +local function keyval_store_get() + local haveany; + local result = {}; + for row in engine:select("SELECT `key`,`type`,`value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store) do + haveany = true; + local k = row[1]; + local v = deserialize(row[2], row[3]); + 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 + end + if haveany then + return result; + end +end +local function keyval_store_set(data) + engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?", host, user, store); + + if data and next(data) ~= nil then + local extradata = {}; + for key, value in pairs(data) do + if type(key) == "string" and key ~= "" then + local t, value = serialize(value); + assert(t, value); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, key, t, value); + else + extradata[key] = value; + end + end + if next(extradata) ~= nil then + local t, extradata = serialize(extradata); + assert(t, extradata); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, user, store, "", t, extradata); + end + end + return true; +end + +local keyval_store = {}; +keyval_store.__index = keyval_store; +function keyval_store:get(username) + user,store = username,self.store; + return select(2, engine:transaction(keyval_store_get)); +end +function keyval_store:set(username, data) + user,store = username,self.store; + return engine:transaction(function() + return keyval_store_set(data); + end); +end +function keyval_store:users() + return engine:transaction(function() + return engine:select("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store); + end); +end + +local driver = {}; + +function driver:open(store, typ) + if not typ then -- default key-value store + return setmetatable({ store = store }, keyval_store); + end + return nil, "unsupported-store"; +end + +function driver:stores(username) + local sql = "SELECT DISTINCT `store` FROM `prosody` WHERE `host`=? AND `user`" .. + (username == true and "!=?" or "=?"); + if username == true or not username then + username = ""; + end + return engine:transaction(function() + return engine:select(sql, host, username); + end); +end + +function driver:purge(username) + return engine:transaction(function() + local stmt,err = engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username); + return true,err; + end); +end + +module:provides("storage", driver); + + diff --git a/plugins/sql.lib.lua b/plugins/sql.lib.lua new file mode 100644 index 00000000..005ee45d --- /dev/null +++ b/plugins/sql.lib.lua @@ -0,0 +1,9 @@ +local cache = module:shared("/*/sql.lib/util.sql"); + +if not cache._M then + prosody.unlock_globals(); + cache._M = require "util.sql"; + prosody.lock_globals(); +end + +return cache._M; diff --git a/util/sql.lua b/util/sql.lua new file mode 100644 index 00000000..f360d6d0 --- /dev/null +++ b/util/sql.lua @@ -0,0 +1,340 @@ + +local setmetatable, getmetatable = setmetatable, getmetatable; +local ipairs, unpack, select = ipairs, unpack, select; +local tonumber, tostring = tonumber, tostring; +local assert, xpcall, debug_traceback = assert, 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; + +module("sql") + +local column_mt = {}; +local table_mt = {}; +local query_mt = {}; +--local op_mt = {}; +local index_mt = {}; + +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 +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 +function Index(definition) + return setmetatable(definition, index_mt); +end + +function table_mt:__tostring() + local s = { 'name="'..self.__table__.name..'"' } + for i,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 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") + local dbh, err = DBI.Connect( + params.driver, params.database, + params.username, params.password, + params.host, params.port + ); + if not dbh then return nil, err; end + dbh:autocommit(false); -- don't commit automatically + self.conn = dbh; + self.prepared = {}; + return true; +end +function engine:execute(sql, ...) + local success, err = self:connect(); + if not success then return success, err; end + local prepared = self.prepared; + + 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.__affected; end; + rowcount = function(self) return self.__rowcount; end; +} }; + +function engine:execute_query(sql, ...) + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + local stmt = assert(self.conn:prepare(sql)); + assert(stmt:execute(...)); + return stmt:rows(); +end +function engine:execute_update(sql, ...) + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + 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({ __affected = stmt:affected(), __rowcount = stmt:rowcount() }, result_mt); +end +engine.insert = engine.execute_update; +engine.select = engine.execute_query; +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 + 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 + self.__transaction = true; + local success, a, b, c = xpcall(f, debug_traceback); + 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 a,b = self:_transaction(...); + if not a then + local conn = self.conn; + if not conn or not conn:ping() then + self.conn = nil; + a,b = self:_transaction(...); + end + end + return a,b; +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 == "PostgreSQL" then + sql = sql:gsub("`", "\""); + elseif self.params.driver == "MySQL" then + sql = sql:gsub("`([,)])", "`(20)%1"); + 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; + if col.nullable == false then sql = sql.." NOT NULL"; end + if i ~= #table.c then sql = sql..", "; end + end + sql = sql.. ");" + if self.params.driver == "PostgreSQL" then + sql = sql:gsub("`", "\""); + end + local success,err = self:execute(sql); + if not success then return success,err; end + for i,v in ipairs(table.__table__) do + if is_index(v) then + self:_create_index(v); + end + end + return success; +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 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']) +end +--result.close();]] + +return _M; -- cgit v1.2.3 From f74c3034b04030ac32d082e00f19427ac5583942 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 19 Apr 2013 13:29:47 +0100 Subject: moduleapi: Add module:open_store() as a front-end to storagemanager.open() --- core/moduleapi.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index fa20c3cd..a8e6881e 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -337,4 +337,8 @@ function api:load_resource(path, mode) return io.open(path, mode); end +function api:open_store(name, type) + return storagemanager.open(self.host, name, type); +end + return api; -- cgit v1.2.3 From 1cf3761516d65ee399a027aebf7b94756c6a2e4d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 19 Apr 2013 13:29:47 +0100 Subject: moduleapi: Add module:open_store() as a front-end to storagemanager.open() --- core/moduleapi.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index fa20c3cd..a8e6881e 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -337,4 +337,8 @@ function api:load_resource(path, mode) return io.open(path, mode); end +function api:open_store(name, type) + return storagemanager.open(self.host, name, type); +end + return api; -- cgit v1.2.3 From 24f2980473e099d9b6f1b15682f33d70a89a5cab Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2013 14:42:32 +0200 Subject: moduleapi: Make module:open_store() open a store named after the calling module by default --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index a8e6881e..30360f73 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -338,7 +338,7 @@ function api:load_resource(path, mode) end function api:open_store(name, type) - return storagemanager.open(self.host, name, type); + return storagemanager.open(self.host, name or self.name, type); end return api; -- cgit v1.2.3 From ba7c2c75c4d6d967f97ba58f26062feb37b9041a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2013 16:14:06 +0200 Subject: mod_auth_internal_hashed, mod_auth_internal_plain, mod_privacy, mod_private, mod_register, mod_vcard, mod_muc: Use module:open_store() --- plugins/mod_auth_internal_hashed.lua | 23 ++++++++++++----------- plugins/mod_auth_internal_plain.lua | 19 ++++++++++--------- plugins/mod_privacy.lua | 9 +++++---- plugins/mod_private.lua | 6 +++--- plugins/mod_register.lua | 5 +++-- plugins/mod_vcard.lua | 9 +++++---- plugins/muc/mod_muc.lua | 15 ++++++++------- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua index cb6cc8ff..2b041e43 100644 --- a/plugins/mod_auth_internal_hashed.lua +++ b/plugins/mod_auth_internal_hashed.lua @@ -7,13 +7,14 @@ -- COPYING file in the source package for more information. -- -local datamanager = require "util.datamanager"; local log = require "util.logger".init("auth_internal_hashed"); local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1; local usermanager = require "core.usermanager"; local generate_uuid = require "util.uuid".generate; local new_sasl = require "util.sasl".new; +local accounts = module:open_store("accounts"); + local to_hex; do local function replace_byte_with_hex(byte) @@ -44,7 +45,7 @@ local provider = {}; log("debug", "initializing internal_hashed authentication provider for host '%s'", host); function provider.test_password(username, password) - local credentials = datamanager.load(username, host, "accounts") or {}; + local credentials = accounts:get(username) or {}; if credentials.password ~= nil and string.len(credentials.password) ~= 0 then if credentials.password ~= password then @@ -75,7 +76,7 @@ function provider.test_password(username, password) end function provider.set_password(username, password) - local account = datamanager.load(username, host, "accounts"); + local account = accounts:get(username); if account then account.salt = account.salt or generate_uuid(); account.iteration_count = account.iteration_count or iteration_count; @@ -87,13 +88,13 @@ function provider.set_password(username, password) account.server_key = server_key_hex account.password = nil; - return datamanager.store(username, host, "accounts", account); + return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) - local account = datamanager.load(username, host, "accounts"); + local account = accounts:get(username); if not account then log("debug", "account not found for username '%s' at host '%s'", username, host); return nil, "Auth failed. Invalid username"; @@ -102,22 +103,22 @@ function provider.user_exists(username) end function provider.users() - return datamanager.users(host, "accounts"); + return accounts:users(); end function provider.create_user(username, password) if password == nil then - return datamanager.store(username, host, "accounts", {}); + return accounts:set(username, {}); end local salt = generate_uuid(); local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); - return datamanager.store(username, host, "accounts", {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count}); + return accounts:set(username, {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count}); end function provider.delete_user(username) - return datamanager.store(username, host, "accounts", nil); + return accounts:set(username, nil); end function provider.get_sasl_handler() @@ -126,11 +127,11 @@ function provider.get_sasl_handler() return usermanager.test_password(username, realm, password), true; end, scram_sha_1 = function(sasl, username, realm) - local credentials = datamanager.load(username, host, "accounts"); + local credentials = accounts:get(username); if not credentials then return; end if credentials.password then usermanager.set_password(username, credentials.password, host); - credentials = datamanager.load(username, host, "accounts"); + credentials = accounts:get(username); if not credentials then return; end end diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua index 178ae5a5..e411c4f7 100644 --- a/plugins/mod_auth_internal_plain.lua +++ b/plugins/mod_auth_internal_plain.lua @@ -6,20 +6,21 @@ -- COPYING file in the source package for more information. -- -local datamanager = require "util.datamanager"; local usermanager = require "core.usermanager"; local new_sasl = require "util.sasl".new; local log = module._log; local host = module.host; +local accounts = module:open_store("accounts"); + -- define auth provider local provider = {}; log("debug", "initializing internal_plain authentication provider for host '%s'", host); function provider.test_password(username, password) log("debug", "test password '%s' for user %s at host %s", password, username, host); - local credentials = datamanager.load(username, host, "accounts") or {}; + local credentials = accounts:get(username) or {}; if password == credentials.password then return true; @@ -30,20 +31,20 @@ end function provider.get_password(username) log("debug", "get_password for username '%s' at host '%s'", username, host); - return (datamanager.load(username, host, "accounts") or {}).password; + return (accounts:get(username) or {}).password; end function provider.set_password(username, password) - local account = datamanager.load(username, host, "accounts"); + local account = accounts:get(username); if account then account.password = password; - return datamanager.store(username, host, "accounts", account); + return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) - local account = datamanager.load(username, host, "accounts"); + local account = accounts:get(username); if not account then log("debug", "account not found for username '%s' at host '%s'", username, host); return nil, "Auth failed. Invalid username"; @@ -52,15 +53,15 @@ function provider.user_exists(username) end function provider.users() - return datamanager.users(host, "accounts"); + return accounts:users(); end function provider.create_user(username, password) - return datamanager.store(username, host, "accounts", {password = password}); + return accounts:set(username, {password = password}); end function provider.delete_user(username) - return datamanager.store(username, host, "accounts", nil); + return accounts:set(username, nil); end function provider.get_sasl_handler() diff --git a/plugins/mod_privacy.lua b/plugins/mod_privacy.lua index dc6b153a..31ace9f9 100644 --- a/plugins/mod_privacy.lua +++ b/plugins/mod_privacy.lua @@ -10,7 +10,6 @@ module:add_feature("jabber:iq:privacy"); local st = require "util.stanza"; -local datamanager = require "util.datamanager"; local bare_sessions, full_sessions = prosody.bare_sessions, prosody.full_sessions; local util_Jid = require "util.jid"; local jid_bare = util_Jid.bare; @@ -18,6 +17,8 @@ local jid_split, jid_join = util_Jid.split, util_Jid.join; local load_roster = require "core.rostermanager".load_roster; local to_number = tonumber; +local privacy_storage = module:open_store(); + function isListUsed(origin, name, privacy_lists) local user = bare_sessions[origin.username.."@"..origin.host]; if user then @@ -217,7 +218,7 @@ module:hook("iq/bare/jabber:iq:privacy:query", function(data) if stanza.attr.to == nil then -- only service requests to own bare JID local query = stanza.tags[1]; -- the query element local valid = false; - local privacy_lists = datamanager.load(origin.username, origin.host, "privacy") or { lists = {} }; + local privacy_lists = privacy_storage:get(origin.username) or { lists = {} }; if privacy_lists.lists[1] then -- Code to migrate from old privacy lists format, remove in 0.8 module:log("info", "Upgrading format of stored privacy lists for %s@%s", origin.username, origin.host); @@ -272,7 +273,7 @@ module:hook("iq/bare/jabber:iq:privacy:query", function(data) end origin.send(st.error_reply(stanza, valid[1], valid[2], valid[3])); else - datamanager.store(origin.username, origin.host, "privacy", privacy_lists); + privacy_storage:set(origin.username, privacy_lists); end return true; end @@ -280,7 +281,7 @@ end); function checkIfNeedToBeBlocked(e, session) local origin, stanza = e.origin, e.stanza; - local privacy_lists = datamanager.load(session.username, session.host, "privacy") or {}; + local privacy_lists = privacy_storage:get(session.username) or {}; local bare_jid = session.username.."@"..session.host; local to = stanza.attr.to or bare_jid; local from = stanza.attr.from; diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua index 29d3162c..365a997c 100644 --- a/plugins/mod_private.lua +++ b/plugins/mod_private.lua @@ -9,7 +9,7 @@ local st = require "util.stanza" -local datamanager = require "util.datamanager" +local private_storage = module:open_store(); module:add_feature("jabber:iq:private"); @@ -20,7 +20,7 @@ module:hook("iq/self/jabber:iq:private:query", function(event) if #query.tags == 1 then local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; - local data, err = datamanager.load(origin.username, origin.host, "private"); + local data, err = private_storage:get(origin.username); if err then origin.send(st.error_reply(stanza, "wait", "internal-server-error")); return true; @@ -39,7 +39,7 @@ module:hook("iq/self/jabber:iq:private:query", function(event) data[key] = st.preserialize(tag); end -- TODO delete datastore if empty - if datamanager.store(origin.username, origin.host, "private", data) then + if private_storage:set(origin.username, data) then origin.send(st.reply(stanza)); else origin.send(st.error_reply(stanza, "wait", "internal-server-error")); diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua index e941a128..141a4997 100644 --- a/plugins/mod_register.lua +++ b/plugins/mod_register.lua @@ -8,7 +8,6 @@ local st = require "util.stanza"; -local datamanager = require "util.datamanager"; local dataform_new = require "util.dataforms".new; local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; @@ -22,6 +21,8 @@ local compat = module:get_option_boolean("registration_compat", true); local allow_registration = module:get_option_boolean("allow_registration", false); local additional_fields = module:get_option("additional_registration_fields", {}); +local account_details = module:open_store("account_details"); + local field_map = { username = { name = "username", type = "text-single", label = "Username", required = true }; password = { name = "password", type = "text-private", label = "Password", required = true }; @@ -234,7 +235,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) -- TODO unable to write file, file may be locked, etc, what's the correct error? local error_reply = st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk."); if usermanager_create_user(username, password, host) then - if next(data) and not datamanager.store(username, host, "account_details", data) then + if next(data) and not account_details:set(username, data) then usermanager_delete_user(username, host); session.send(error_reply); return true; diff --git a/plugins/mod_vcard.lua b/plugins/mod_vcard.lua index d3c27cc0..26b30e3a 100644 --- a/plugins/mod_vcard.lua +++ b/plugins/mod_vcard.lua @@ -8,7 +8,8 @@ local st = require "util.stanza" local jid_split = require "util.jid".split; -local datamanager = require "util.datamanager" + +local vcards = module:open_store(); module:add_feature("vcard-temp"); @@ -19,9 +20,9 @@ local function handle_vcard(event) local vCard; if to then local node, host = jid_split(to); - vCard = st.deserialize(datamanager.load(node, host, "vcard")); -- load vCard for user or server + vCard = st.deserialize(vcards:get(node)); -- load vCard for user or server else - vCard = st.deserialize(datamanager.load(session.username, session.host, "vcard"));-- load user's own vCard + vCard = st.deserialize(vcards:get(session.username));-- load user's own vCard end if vCard then session.send(st.reply(stanza):add_child(vCard)); -- send vCard! @@ -30,7 +31,7 @@ local function handle_vcard(event) end else if not to then - if datamanager.store(session.username, session.host, "vcard", st.preserialize(stanza.tags[1])) then + if vcards:set(session.username, st.preserialize(stanza.tags[1])) then session.send(st.reply(stanza)); else -- TODO unable to write file, file may be locked, etc, what's the correct error? diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 2c2d02f7..7861092c 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -28,13 +28,14 @@ local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local uuid_gen = require "util.uuid".generate; -local datamanager = require "util.datamanager"; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; rooms = {}; local rooms = rooms; -local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {}; +local persistent_rooms_storage = module:open_store("persistent"); +local persistent_rooms = persistent_rooms_storage:get() or {}; +local room_configs = module:open_store("config"); -- Configurable options muclib.set_max_history_length(module:get_option_number("max_history_messages")); @@ -66,15 +67,15 @@ local function room_save(room, forced) _data = room._data; _affiliations = room._affiliations; }; - datamanager.store(node, muc_host, "config", data); + room_configs:set(node, data); room._data.history = history; elseif forced then - datamanager.store(node, muc_host, "config", nil); + room_configs:set(node, nil); if not next(room._occupants) then -- Room empty rooms[room.jid] = nil; end end - if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end + if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end function create_room(jid) @@ -88,7 +89,7 @@ end local persistent_errors = false; for jid in pairs(persistent_rooms) do local node = jid_split(jid); - local data = datamanager.load(node, muc_host, "config"); + local data = room_configs:get(node); if data then local room = create_room(jid); room._data = data._data; @@ -99,7 +100,7 @@ for jid in pairs(persistent_rooms) do persistent_errors = true; end end -if persistent_errors then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end +if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end local host_room = muc_new_room(muc_host); host_room.route_stanza = room_route_stanza; -- cgit v1.2.3 From 0e439624d3641a56584bfb67f194cc091985682b Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Mon, 22 Apr 2013 11:54:15 -0400 Subject: storagemanager: Fix traceback in logging when store type is nil, and store is unsupported. --- core/storagemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/storagemanager.lua b/core/storagemanager.lua index 36a671be..1c82af6d 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -86,7 +86,7 @@ function open(host, store, typ) if not ret then if err == "unsupported-store" then log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver", - driver_name, store, typ); + driver_name, store, typ or ""); ret = null_storage_driver; err = nil; end -- cgit v1.2.3 From a0756d32d7cbdb490ae3feabf849caffa77af7a1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:24:42 +0100 Subject: net.server.http: Ensure that event map cannot grow forever (limit to 10K wildcard-only entries) --- net/http/server.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index a983b8d5..3c2b55d5 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -27,6 +27,8 @@ local function is_wildcard_match(wildcard_event, event) return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1); end +local recent_wildcard_events, max_cached_wildcard_events = {}, 10000; + local event_map = events._event_map; setmetatable(events._handlers, { __index = function (handlers, curr_event) @@ -58,6 +60,12 @@ setmetatable(events._handlers, { handlers_array = false; end rawset(handlers, curr_event, handlers_array); + if not event_map[curr_event] then -- Only wildcard handlers match, if any + table.insert(recent_wildcard_events, curr_event); + if #recent_wildcard_events > max_cached_wildcard_events then + rawset(handlers, table.remove(recent_wildcard_events, 1), nil); + end + end return handlers_array; end; __newindex = function (handlers, curr_event, handlers_array) -- cgit v1.2.3 From ee1ddadb4c2747bf4b74b4b410833702d1cd4a97 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:24:42 +0100 Subject: net.server.http: Ensure that event map cannot grow forever (limit to 10K wildcard-only entries) --- net/http/server.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index a983b8d5..3c2b55d5 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -27,6 +27,8 @@ local function is_wildcard_match(wildcard_event, event) return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1); end +local recent_wildcard_events, max_cached_wildcard_events = {}, 10000; + local event_map = events._event_map; setmetatable(events._handlers, { __index = function (handlers, curr_event) @@ -58,6 +60,12 @@ setmetatable(events._handlers, { handlers_array = false; end rawset(handlers, curr_event, handlers_array); + if not event_map[curr_event] then -- Only wildcard handlers match, if any + table.insert(recent_wildcard_events, curr_event); + if #recent_wildcard_events > max_cached_wildcard_events then + rawset(handlers, table.remove(recent_wildcard_events, 1), nil); + end + end return handlers_array; end; __newindex = function (handlers, curr_event, handlers_array) -- cgit v1.2.3 From c4bb55fee4d3915353954e900b8ce07441c5b5d7 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:25:00 +0100 Subject: net.server.http: Add a comment --- net/http/server.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/server.lua b/net/http/server.lua index 3c2b55d5..577b05ef 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -31,6 +31,7 @@ local recent_wildcard_events, max_cached_wildcard_events = {}, 10000; local event_map = events._event_map; setmetatable(events._handlers, { + -- Called when firing an event that doesn't exist (but may match a wildcard handler) __index = function (handlers, curr_event) if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired -- Find all handlers that could match this event, sort them -- cgit v1.2.3 From 10e2b82e095ad30cb755117e0ae3e60c578ec615 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:25:00 +0100 Subject: net.server.http: Add a comment --- net/http/server.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/server.lua b/net/http/server.lua index 3c2b55d5..577b05ef 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -31,6 +31,7 @@ local recent_wildcard_events, max_cached_wildcard_events = {}, 10000; local event_map = events._event_map; setmetatable(events._handlers, { + -- Called when firing an event that doesn't exist (but may match a wildcard handler) __index = function (handlers, curr_event) if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired -- Find all handlers that could match this event, sort them -- cgit v1.2.3 From f0c895100d8728a7832a16f9824c003b461d5a22 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:35:52 +0100 Subject: mod_c2s, mod_s2s, net.http, net.http.server: Improve tracebacks (omit traceback function), to make it clearer where an error occured --- net/http.lua | 8 ++++---- net/http/server.lua | 4 ++-- plugins/mod_c2s.lua | 2 +- plugins/mod_s2s/mod_s2s.lua | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/net/http.lua b/net/http.lua index 639ecf6a..3b783a41 100644 --- a/net/http.lua +++ b/net/http.lua @@ -17,9 +17,9 @@ local ssl_available = pcall(require, "ssl"); local server = require "net.server" local t_insert, t_concat = table.insert, table.concat; -local pairs, ipairs = pairs, ipairs; -local tonumber, tostring, xpcall, select, debug_traceback, char, format = - tonumber, tostring, xpcall, select, debug.traceback, string.char, string.format; +local pairs = pairs; +local tonumber, tostring, xpcall, select, traceback = + tonumber, tostring, xpcall, select, debug.traceback; local log = require "util.logger".init("http"); @@ -101,7 +101,7 @@ local function request_reader(request, data, err) request.parser:feed(data); end -local function handleerr(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug_traceback()); end +local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); end function request(u, ex, callback) local req = url.parse(u); diff --git a/net/http/server.lua b/net/http/server.lua index 577b05ef..dec7da19 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -9,7 +9,7 @@ local pairs = pairs; local s_upper = string.upper; local setmetatable = setmetatable; local xpcall = xpcall; -local debug = debug; +local traceback = debug.traceback; local tostring = tostring; local codes = require "net.http.codes"; @@ -88,7 +88,7 @@ local _1, _2, _3; local function _handle_request() return handle_request(_1, _2, _3); end local last_err; -local function _traceback_handler(err) last_err = err; log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end +local function _traceback_handler(err) last_err = err; log("error", "Traceback[httpserver]: %s", traceback(tostring(err), 2)); end events.add_handler("http-error", function (error) return "Error processing request: "..codes[error.code]..". Check your error log for more information."; end, -1); diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 4a3197d9..cafd0c71 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -116,7 +116,7 @@ function stream_callbacks.error(session, error, data) end end -local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end +local function handleerr(err) log("error", "Traceback[c2s]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(session, stanza) stanza = session.filter("stanzas/in", stanza); if stanza then diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6764e857..0ece23a6 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -429,7 +429,7 @@ function stream_callbacks.error(session, error, data) end end -local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end +local function handleerr(err) log("error", "Traceback[s2s]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(session, stanza) if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client stanza.attr.xmlns = nil; -- cgit v1.2.3 From 093cf12d36b125db7afa71a4b7bdffd05c42fb71 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Apr 2013 12:35:52 +0100 Subject: mod_c2s, mod_s2s, net.http, net.http.server: Improve tracebacks (omit traceback function), to make it clearer where an error occured --- net/http.lua | 8 ++++---- net/http/server.lua | 4 ++-- plugins/mod_c2s.lua | 2 +- plugins/mod_s2s/mod_s2s.lua | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/net/http.lua b/net/http.lua index 639ecf6a..3b783a41 100644 --- a/net/http.lua +++ b/net/http.lua @@ -17,9 +17,9 @@ local ssl_available = pcall(require, "ssl"); local server = require "net.server" local t_insert, t_concat = table.insert, table.concat; -local pairs, ipairs = pairs, ipairs; -local tonumber, tostring, xpcall, select, debug_traceback, char, format = - tonumber, tostring, xpcall, select, debug.traceback, string.char, string.format; +local pairs = pairs; +local tonumber, tostring, xpcall, select, traceback = + tonumber, tostring, xpcall, select, debug.traceback; local log = require "util.logger".init("http"); @@ -101,7 +101,7 @@ local function request_reader(request, data, err) request.parser:feed(data); end -local function handleerr(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug_traceback()); end +local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); end function request(u, ex, callback) local req = url.parse(u); diff --git a/net/http/server.lua b/net/http/server.lua index 577b05ef..dec7da19 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -9,7 +9,7 @@ local pairs = pairs; local s_upper = string.upper; local setmetatable = setmetatable; local xpcall = xpcall; -local debug = debug; +local traceback = debug.traceback; local tostring = tostring; local codes = require "net.http.codes"; @@ -88,7 +88,7 @@ local _1, _2, _3; local function _handle_request() return handle_request(_1, _2, _3); end local last_err; -local function _traceback_handler(err) last_err = err; log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end +local function _traceback_handler(err) last_err = err; log("error", "Traceback[httpserver]: %s", traceback(tostring(err), 2)); end events.add_handler("http-error", function (error) return "Error processing request: "..codes[error.code]..". Check your error log for more information."; end, -1); diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 4a3197d9..cafd0c71 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -116,7 +116,7 @@ function stream_callbacks.error(session, error, data) end end -local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end +local function handleerr(err) log("error", "Traceback[c2s]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(session, stanza) stanza = session.filter("stanzas/in", stanza); if stanza then diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6764e857..0ece23a6 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -429,7 +429,7 @@ function stream_callbacks.error(session, error, data) end end -local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end +local function handleerr(err) log("error", "Traceback[s2s]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(session, stanza) if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client stanza.attr.xmlns = nil; -- cgit v1.2.3 From a6230368d4fdbb91b935661871bad0e65973e397 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 23 Apr 2013 15:13:51 +0100 Subject: mod_auth_internal_plain: Don't log passwords, even at debug level --- plugins/mod_auth_internal_plain.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua index e411c4f7..d226fdbe 100644 --- a/plugins/mod_auth_internal_plain.lua +++ b/plugins/mod_auth_internal_plain.lua @@ -19,7 +19,7 @@ local provider = {}; log("debug", "initializing internal_plain authentication provider for host '%s'", host); function provider.test_password(username, password) - log("debug", "test password '%s' for user %s at host %s", password, username, host); + log("debug", "test password for user %s at host %s", username, host); local credentials = accounts:get(username) or {}; if password == credentials.password then -- cgit v1.2.3 From 81a266b6b7a97cf9d5905d249549c851ff99fd85 Mon Sep 17 00:00:00 2001 From: Florian Zeitz Date: Tue, 23 Apr 2013 14:49:31 +0200 Subject: util.adhoc: New util for generating common adhoc handler patterns --- util/adhoc.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 util/adhoc.lua diff --git a/util/adhoc.lua b/util/adhoc.lua new file mode 100644 index 00000000..671e85cf --- /dev/null +++ b/util/adhoc.lua @@ -0,0 +1,31 @@ +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() } }, "executing"; + end + end +end + +return { new_simple_form = new_simple_form, + new_initial_data_form = new_initial_data_form }; -- cgit v1.2.3 From 24d0af7002245c77ce225e0f18ca337998772e59 Mon Sep 17 00:00:00 2001 From: Florian Zeitz Date: Tue, 23 Apr 2013 14:49:48 +0200 Subject: mod_admin_adhoc: Use util.adhoc --- plugins/mod_admin_adhoc.lua | 1173 +++++++++++++++++++------------------------ 1 file changed, 530 insertions(+), 643 deletions(-) diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua index ff672fc9..31c4bde4 100644 --- a/plugins/mod_admin_adhoc.lua +++ b/plugins/mod_admin_adhoc.lua @@ -10,6 +10,8 @@ local prosody = _G.prosody; local hosts = prosody.hosts; local t_concat = table.concat; +local module_host = module:get_host(); + local keys = require "util.iterators".keys; local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; @@ -25,6 +27,8 @@ local dataforms_new = require "util.dataforms".new; local array = require "util.array"; local modulemanager = require "modulemanager"; local core_post_stanza = prosody.core_post_stanza; +local adhoc_simple = require "util.adhoc".new_simple_form; +local adhoc_initial = require "util.adhoc".new_initial_data_form; module:depends("adhoc"); local adhoc_new = module:require "adhoc".new; @@ -37,82 +41,69 @@ local function generate_error_message(errors) return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end -function add_user_command_handler(self, data, state) - local add_user_layout = dataforms_new{ - title = "Adding a User"; - instructions = "Fill out this form to add a user."; +-- Adding a new user +local add_user_layout = dataforms_new{ + title = "Adding a User"; + instructions = "Fill out this form to add a user."; - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" }; - { name = "password", type = "text-private", label = "The password for this account" }; - { name = "password-verify", type = "text-private", label = "Retype password" }; - }; + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" }; + { name = "password", type = "text-private", label = "The password for this account" }; + { name = "password-verify", type = "text-private", label = "Retype password" }; +}; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = add_user_layout:data(data.form); - if err then - return generate_error_message(err); - end - local username, host, resource = jid.split(fields.accountjid); - if data.to ~= host then - return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}}; - end - if (fields["password"] == fields["password-verify"]) and username and host then - if usermanager_user_exists(username, host) then - return { status = "completed", error = { message = "Account already exists" } }; +local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, err) + if err then + return generate_error_message(err); + end + local username, host, resource = jid.split(fields.accountjid); + if module_host ~= host then + return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}}; + end + if (fields["password"] == fields["password-verify"]) and username and host then + if usermanager_user_exists(username, host) then + return { status = "completed", error = { message = "Account already exists" } }; + else + if usermanager_create_user(username, fields.password, host) then + module:log("info", "Created new account %s@%s", username, host); + return { status = "completed", info = "Account successfully created" }; else - if usermanager_create_user(username, fields.password, host) then - module:log("info", "Created new account %s@%s", username, host); - return { status = "completed", info = "Account successfully created" }; - else - return { status = "completed", error = { message = "Failed to write data to disk" } }; - end + return { status = "completed", error = { message = "Failed to write data to disk" } }; end - else - module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or ""); - return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } }; end else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = add_user_layout }, "executing"; + module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or ""); + return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } }; end -end +end); -function change_user_password_command_handler(self, data, state) - local change_user_password_layout = dataforms_new{ - title = "Changing a User Password"; - instructions = "Fill out this form to change a user's password."; +-- Changing a user's password +local change_user_password_layout = dataforms_new{ + title = "Changing a User Password"; + instructions = "Fill out this form to change a user's password."; - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" }; - { name = "password", type = "text-private", required = true, label = "The password for this account" }; - }; + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" }; + { name = "password", type = "text-private", required = true, label = "The password for this account" }; +}; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = change_user_password_layout:data(data.form); - if err then - return generate_error_message(err); - end - local username, host, resource = jid.split(fields.accountjid); - if data.to ~= host then - return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}}; - end - if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then - return { status = "completed", info = "Password successfully changed" }; - else - return { status = "completed", error = { message = "User does not exist" } }; - end +local change_user_password_command_handler = adhoc_simple(change_user_password_layout, function(fields, err) + if err then + return generate_error_message(err); + end + local username, host, resource = jid.split(fields.accountjid); + if module_host ~= host then + return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}}; + end + if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then + return { status = "completed", info = "Password successfully changed" }; else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = change_user_password_layout }, "executing"; + return { status = "completed", error = { message = "User does not exist" } }; end -end +end); -function config_reload_handler(self, data, state) +-- Reloading the config +local function config_reload_handler(self, data, state) local ok, err = prosody.reload_config(); if ok then return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" }; @@ -121,46 +112,39 @@ function config_reload_handler(self, data, state) end end +-- Deleting a user's account +local delete_user_layout = dataforms_new{ + title = "Deleting a User"; + instructions = "Fill out this form to delete a user."; -function delete_user_command_handler(self, data, state) - local delete_user_layout = dataforms_new{ - title = "Deleting a User"; - instructions = "Fill out this form to delete a user."; + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" }; +}; - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" }; - }; - - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = delete_user_layout:data(data.form); - if err then - return generate_error_message(err); - end - local failed = {}; - local succeeded = {}; - for _, aJID in ipairs(fields.accountjids) do - local username, host, resource = jid.split(aJID); - if (host == data.to) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then - module:log("debug", "User %s has been deleted", aJID); - succeeded[#succeeded+1] = aJID; - else - module:log("debug", "Tried to delete non-existant user %s", aJID); - failed[#failed+1] = aJID; - end +local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fields, err) + if err then + return generate_error_message(err); + end + local failed = {}; + local succeeded = {}; + for _, aJID in ipairs(fields.accountjids) do + local username, host, resource = jid.split(aJID); + if (host == module_host) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then + module:log("debug", "User %s has been deleted", aJID); + succeeded[#succeeded+1] = aJID; + else + module:log("debug", "Tried to delete non-existant user %s", aJID); + failed[#failed+1] = aJID; end - return {status = "completed", info = (#succeeded ~= 0 and - "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "").. - (#failed ~= 0 and - "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = delete_user_layout }, "executing"; end -end - -function disconnect_user(match_jid) + return {status = "completed", info = (#succeeded ~= 0 and + "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "").. + (#failed ~= 0 and + "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") }; +end); + +-- Ending a user's session +local function disconnect_user(match_jid) local node, hostname, givenResource = jid.split(match_jid); local host = hosts[hostname]; local sessions = host.sessions[node] and host.sessions[node].sessions; @@ -173,447 +157,382 @@ function disconnect_user(match_jid) return true; end -function end_user_session_handler(self, data, state) - local end_user_session_layout = dataforms_new{ - title = "Ending a User Session"; - instructions = "Fill out this form to end a user's session."; +local end_user_session_layout = dataforms_new{ + title = "Ending a User Session"; + instructions = "Fill out this form to end a user's session."; - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" }; - }; - - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" }; +}; - local fields, err = end_user_session_layout:data(data.form); - if err then - return generate_error_message(err); - end - local failed = {}; - local succeeded = {}; - for _, aJID in ipairs(fields.accountjids) do - local username, host, resource = jid.split(aJID); - if (host == data.to) and usermanager_user_exists(username, host) and disconnect_user(aJID) then - succeeded[#succeeded+1] = aJID; - else - failed[#failed+1] = aJID; - end - end - return {status = "completed", info = (#succeeded ~= 0 and - "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "").. - (#failed ~= 0 and - "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = end_user_session_layout }, "executing"; +local end_user_session_handler = adhoc_simple(end_user_session_layout, function(fields, err) + if err then + return generate_error_message(err); end -end - -function get_user_password_handler(self, data, state) - local get_user_password_layout = dataforms_new{ - title = "Getting User's Password"; - instructions = "Fill out this form to get a user's password."; - - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" }; - }; - - local get_user_password_result_layout = dataforms_new{ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", label = "JID" }; - { name = "password", type = "text-single", label = "Password" }; - }; - - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = get_user_password_layout:data(data.form); - if err then - return generate_error_message(err); - end - local user, host, resource = jid.split(fields.accountjid); - local accountjid = ""; - local password = ""; - if host ~= data.to then - return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. data.to } }; - elseif usermanager_user_exists(user, host) then - accountjid = fields.accountjid; - password = usermanager_get_password(user, host); + local failed = {}; + local succeeded = {}; + for _, aJID in ipairs(fields.accountjids) do + local username, host, resource = jid.split(aJID); + if (host == module_host) and usermanager_user_exists(username, host) and disconnect_user(aJID) then + succeeded[#succeeded+1] = aJID; else - return { status = "completed", error = { message = "User does not exist" } }; + failed[#failed+1] = aJID; end - return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } }; + end + return {status = "completed", info = (#succeeded ~= 0 and + "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "").. + (#failed ~= 0 and + "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") }; +end); + +-- Getting a user's password +local get_user_password_layout = dataforms_new{ + title = "Getting User's Password"; + instructions = "Fill out this form to get a user's password."; + + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" }; +}; + +local get_user_password_result_layout = dataforms_new{ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", label = "JID" }; + { name = "password", type = "text-single", label = "Password" }; +}; + +local get_user_password_handler = adhoc_simple(get_user_password_layout, function(fields, err) + if err then + return generate_error_message(err); + end + local user, host, resource = jid.split(fields.accountjid); + local accountjid = ""; + local password = ""; + if host ~= module_host then + return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. module_host } }; + elseif usermanager_user_exists(user, host) then + accountjid = fields.accountjid; + password = usermanager_get_password(user, host); else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing"; + return { status = "completed", error = { message = "User does not exist" } }; + end + return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } }; +end); + +-- Getting a user's roster +local get_user_roster_layout = dataforms_new{ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" }; +}; + +local get_user_roster_result_layout = dataforms_new{ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", label = "This is the roster for" }; + { name = "roster", type = "text-multi", label = "Roster XML" }; +}; + +local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fields, err) + if err then + return generate_error_message(err); end -end - -function get_user_roster_handler(self, data, state) - local get_user_roster_layout = dataforms_new{ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" }; - }; - - local get_user_roster_result_layout = dataforms_new{ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", label = "This is the roster for" }; - { name = "roster", type = "text-multi", label = "Roster XML" }; - }; - - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - - local fields, err = get_user_roster_layout:data(data.form); - - if err then - return generate_error_message(err); - end - local user, host, resource = jid.split(fields.accountjid); - if host ~= data.to then - return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } }; - elseif not usermanager_user_exists(user, host) then - return { status = "completed", error = { message = "User does not exist" } }; - end - local roster = rm_load_roster(user, host); - - local query = st.stanza("query", { xmlns = "jabber:iq:roster" }); - for jid in pairs(roster) do - if jid ~= "pending" and jid then - query:tag("item", { - jid = jid, - subscription = roster[jid].subscription, - ask = roster[jid].ask, - name = roster[jid].name, - }); - for group in pairs(roster[jid].groups) do - query:tag("group"):text(group):up(); - end - query:up(); + local user, host, resource = jid.split(fields.accountjid); + if host ~= module_host then + return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } }; + elseif not usermanager_user_exists(user, host) then + return { status = "completed", error = { message = "User does not exist" } }; + end + local roster = rm_load_roster(user, host); + + local query = st.stanza("query", { xmlns = "jabber:iq:roster" }); + for jid in pairs(roster) do + if jid ~= "pending" and jid then + query:tag("item", { + jid = jid, + subscription = roster[jid].subscription, + ask = roster[jid].ask, + name = roster[jid].name, + }); + for group in pairs(roster[jid].groups) do + query:tag("group"):text(group):up(); end + query:up(); end - - local query_text = tostring(query):gsub("><", ">\n<"); - - local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result"); - result:add_child(query); - return { status = "completed", other = result }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_roster_layout }, "executing"; end -end -function get_user_stats_handler(self, data, state) - local get_user_stats_layout = dataforms_new{ - title = "Get User Statistics"; - instructions = "Fill out this form to gather user statistics."; + local query_text = tostring(query):gsub("><", ">\n<"); - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" }; - }; + local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result"); + result:add_child(query); + return { status = "completed", other = result }; +end); - local get_user_stats_result_layout = dataforms_new{ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "ipaddresses", type = "text-multi", label = "IP Addresses" }; - { name = "rostersize", type = "text-single", label = "Roster size" }; - { name = "onlineresources", type = "text-multi", label = "Online Resources" }; - }; +-- Getting user statistics +local get_user_stats_layout = dataforms_new{ + title = "Get User Statistics"; + instructions = "Fill out this form to gather user statistics."; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - - local fields, err = get_user_stats_layout:data(data.form); + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" }; +}; - if err then - return generate_error_message(err); - end +local get_user_stats_result_layout = dataforms_new{ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "ipaddresses", type = "text-multi", label = "IP Addresses" }; + { name = "rostersize", type = "text-single", label = "Roster size" }; + { name = "onlineresources", type = "text-multi", label = "Online Resources" }; +}; - local user, host, resource = jid.split(fields.accountjid); - if host ~= data.to then - return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. data.to } }; - elseif not usermanager_user_exists(user, host) then - return { status = "completed", error = { message = "User does not exist" } }; - end - local roster = rm_load_roster(user, host); - local rostersize = 0; - local IPs = ""; - local resources = ""; - for jid in pairs(roster) do - if jid ~= "pending" and jid then - rostersize = rostersize + 1; - end - end - for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do - resources = resources .. "\n" .. resource; - IPs = IPs .. "\n" .. session.ip; - end - return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize), - onlineresources = resources}} }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_stats_layout }, "executing"; +local get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err) + if err then + return generate_error_message(err); end -end -function get_online_users_command_handler(self, data, state) - local get_online_users_layout = dataforms_new{ - title = "Getting List of Online Users"; - instructions = "How many users should be returned at most?"; - - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "max_items", type = "list-single", label = "Maximum number of users", - value = { "25", "50", "75", "100", "150", "200", "all" } }; - { name = "details", type = "boolean", label = "Show details" }; - }; - - local get_online_users_result_layout = dataforms_new{ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" }; - }; - - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - - local fields, err = get_online_users_layout:data(data.form); - - if err then - return generate_error_message(err); + local user, host, resource = jid.split(fields.accountjid); + if host ~= module_host then + return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. module_host } }; + elseif not usermanager_user_exists(user, host) then + return { status = "completed", error = { message = "User does not exist" } }; + end + local roster = rm_load_roster(user, host); + local rostersize = 0; + local IPs = ""; + local resources = ""; + for jid in pairs(roster) do + if jid ~= "pending" and jid then + rostersize = rostersize + 1; end + end + for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do + resources = resources .. "\n" .. resource; + IPs = IPs .. "\n" .. session.ip; + end + return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize), + onlineresources = resources}} }; +end); + +-- Getting a list of online users +local get_online_users_layout = dataforms_new{ + title = "Getting List of Online Users"; + instructions = "How many users should be returned at most?"; + + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "max_items", type = "list-single", label = "Maximum number of users", + value = { "25", "50", "75", "100", "150", "200", "all" } }; + { name = "details", type = "boolean", label = "Show details" }; +}; + +local get_online_users_result_layout = dataforms_new{ + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" }; +}; + +local get_online_users_command_handler = adhoc_simple(get_online_users_layout, function(fields, err) + if err then + return generate_error_message(err); + end - local max_items = nil - if fields.max_items ~= "all" then - max_items = tonumber(fields.max_items); - end - local count = 0; - local users = {}; - for username, user in pairs(hosts[data.to].sessions or {}) do - if (max_items ~= nil) and (count >= max_items) then - break; - end - users[#users+1] = username.."@"..data.to; - count = count + 1; - if fields.details then - for resource, session in pairs(user.sessions or {}) do - local status, priority = "unavailable", tostring(session.priority or "-"); - if session.presence then - status = session.presence:child_with_name("show"); - if status then - status = status:get_text() or "[invalid!]"; - else - status = "available"; - end + local max_items = nil + if fields.max_items ~= "all" then + max_items = tonumber(fields.max_items); + end + local count = 0; + local users = {}; + for username, user in pairs(hosts[module_host].sessions or {}) do + if (max_items ~= nil) and (count >= max_items) then + break; + end + users[#users+1] = username.."@"..module_host; + count = count + 1; + if fields.details then + for resource, session in pairs(user.sessions or {}) do + local status, priority = "unavailable", tostring(session.priority or "-"); + if session.presence then + status = session.presence:child_with_name("show"); + if status then + status = status:get_text() or "[invalid!]"; + else + status = "available"; end - users[#users+1] = " - "..resource..": "..status.."("..priority..")"; end + users[#users+1] = " - "..resource..": "..status.."("..priority..")"; end end - return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_online_users_layout }, "executing"; end -end + return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} }; +end); -function list_modules_handler(self, data, state) - local result = dataforms_new { - title = "List of loaded modules"; +-- Getting a list of loaded modules +local list_modules_result = dataforms_new { + title = "List of loaded modules"; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" }; - { name = "modules", type = "text-multi", label = "The following modules are loaded:" }; - }; - - local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n"); + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" }; + { name = "modules", type = "text-multi", label = "The following modules are loaded:" }; +}; - return { status = "completed", result = { layout = result; values = { modules = modules } } }; +local function list_modules_handler(self, data, state) + local modules = array.collect(keys(hosts[module_host].modules)):sort():concat("\n"); + return { status = "completed", result = { layout = list_modules_result; values = { modules = modules } } }; end -function load_module_handler(self, data, state) - local layout = dataforms_new { - title = "Load module"; - instructions = "Specify the module to be loaded"; +-- Loading a module +local load_module_layout = dataforms_new { + title = "Load module"; + instructions = "Specify the module to be loaded"; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" }; - { name = "module", type = "text-single", required = true, label = "Module to be loaded:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end - if modulemanager.is_loaded(data.to, fields.module) then - return { status = "completed", info = "Module already loaded" }; - end - local ok, err = modulemanager.load(data.to, fields.module); - if ok then - return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' }; - else - return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to.. - '". Error was: "'..tostring(err or "")..'"' } }; - end + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" }; + { name = "module", type = "text-single", required = true, label = "Module to be loaded:"}; +}; + +local load_module_handler = adhoc_simple(load_module_layout, function(fields, err) + if err then + return generate_error_message(err); + end + if modulemanager.is_loaded(module_host, fields.module) then + return { status = "completed", info = "Module already loaded" }; + end + local ok, err = modulemanager.load(module_host, fields.module); + if ok then + return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..module_host..'".' }; else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing"; + return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host.. + '". Error was: "'..tostring(err or "")..'"' } }; end -end - -local function globally_load_module_handler(self, data, state) - local layout = dataforms_new { - title = "Globally load module"; - instructions = "Specify the module to be loaded on all hosts"; - - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" }; - { name = "module", type = "text-single", required = true, label = "Module to globally load:"}; - }; - if state then - local ok_list, err_list = {}, {}; +end); - if data.action == "cancel" then - return { status = "canceled" }; - end +-- Globally loading a module +local globally_load_module_layout = dataforms_new { + title = "Globally load module"; + instructions = "Specify the module to be loaded on all hosts"; - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" }; + { name = "module", type = "text-single", required = true, label = "Module to globally load:"}; +}; - local ok, err = modulemanager.load(data.to, fields.module); - if ok then - ok_list[#ok_list + 1] = data.to; - else - err_list[#err_list + 1] = data.to .. " (Error: " .. tostring(err) .. ")"; - end +local globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err) + local ok_list, err_list = {}, {}; - -- Is this a global module? - if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(data.to, fields.module) then - return { status = "completed", info = 'Global module '..fields.module..' loaded.' }; - end - - -- This is either a shared or "normal" module, load it on all other hosts - for host_name, host in pairs(hosts) do - if host_name ~= data.to and host.type == "local" then - local ok, err = modulemanager.load(host_name, fields.module); - if ok then - ok_list[#ok_list + 1] = host_name; - else - err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; - end - end - end + if err then + return generate_error_message(err); + end - local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "") - .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. - (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or ""); - return { status = "completed", info = info }; + local ok, err = modulemanager.load(module_host, fields.module); + if ok then + ok_list[#ok_list + 1] = module_host; else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing"; + err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")"; end -end -function reload_modules_handler(self, data, state) - local layout = dataforms_new { - title = "Reload modules"; - instructions = "Select the modules to be reloaded"; + -- Is this a global module? + if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(module_host, fields.module) then + return { status = "completed", info = 'Global module '..fields.module..' loaded.' }; + end - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" }; - { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end - local ok_list, err_list = {}, {}; - for _, module in ipairs(fields.modules) do - local ok, err = modulemanager.reload(data.to, module); + -- This is either a shared or "normal" module, load it on all other hosts + for host_name, host in pairs(hosts) do + if host_name ~= module_host and host.type == "local" then + local ok, err = modulemanager.load(host_name, fields.module); if ok then - ok_list[#ok_list + 1] = module; + ok_list[#ok_list + 1] = host_name; else - err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; + err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end - local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "") - .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. - (#err_list > 0 and ("Failed to reload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or ""); - return { status = "completed", info = info }; - else - local modules = array.collect(keys(hosts[data.to].modules)):sort(); - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing"; end -end -local function globally_reload_module_handler(self, data, state) - local layout = dataforms_new { - title = "Globally reload module"; - instructions = "Specify the module to reload on all hosts"; - - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" }; - { name = "module", type = "list-single", required = true, label = "Module to globally reload:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - - local is_global = false; - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); + local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "") + .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. + (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or ""); + return { status = "completed", info = info }; +end); + +-- Reloading modules +local reload_modules_layout = dataforms_new { + title = "Reload modules"; + instructions = "Select the modules to be reloaded"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" }; + { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"}; +}; + +local reload_modules_handler = adhoc_initial(reload_modules_layout, function() + return { modules = array.collect(keys(hosts[module_host].modules)):sort() }; +end, function(fields, err) + if err then + return generate_error_message(err); + end + local ok_list, err_list = {}, {}; + for _, module in ipairs(fields.modules) do + local ok, err = modulemanager.reload(module_host, module); + if ok then + ok_list[#ok_list + 1] = module; + else + err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; end + end + local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "") + .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. + (#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or ""); + return { status = "completed", info = info }; +end); + +-- Globally reloading a module +local globally_reload_module_layout = dataforms_new { + title = "Globally reload module"; + instructions = "Specify the module to reload on all hosts"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" }; + { name = "module", type = "list-single", required = true, label = "Module to globally reload:"}; +}; + +local globally_reload_module_handler = adhoc_initial(globally_reload_module_layout, function() + local loaded_modules = array(keys(modulemanager.get_modules("*"))); + for _, host in pairs(hosts) do + loaded_modules:append(array(keys(host.modules))); + end + loaded_modules = array(keys(set.new(loaded_modules):items())):sort(); + return { module = loaded_modules }; +end, function(fields, err) + local is_global = false; - if modulemanager.is_loaded("*", fields.module) then - local ok, err = modulemanager.reload("*", fields.module); - if not ok then - return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err }; - end - is_global = true; - end + if err then + return generate_error_message(err); + end - local ok_list, err_list = {}, {}; - for host_name, host in pairs(hosts) do - if modulemanager.is_loaded(host_name, fields.module) then - local ok, err = modulemanager.reload(host_name, fields.module); - if ok then - ok_list[#ok_list + 1] = host_name; - else - err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; - end - end + if modulemanager.is_loaded("*", fields.module) then + local ok, err = modulemanager.reload("*", fields.module); + if not ok then + return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err }; end + is_global = true; + end - if #ok_list == 0 and #err_list == 0 then - if is_global then - return { status = "completed", info = 'Successfully reloaded global module '..fields.module }; + local ok_list, err_list = {}, {}; + for host_name, host in pairs(hosts) do + if modulemanager.is_loaded(host_name, fields.module) then + local ok, err = modulemanager.reload(host_name, fields.module); + if ok then + ok_list[#ok_list + 1] = host_name; else - return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; + err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end + end - local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") - .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. - (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); - return { status = "completed", info = info }; - else - local loaded_modules = array(keys(modulemanager.get_modules("*"))); - for _, host in pairs(hosts) do - loaded_modules:append(array(keys(host.modules))); + if #ok_list == 0 and #err_list == 0 then + if is_global then + return { status = "completed", info = 'Successfully reloaded global module '..fields.module }; + else + return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; end - loaded_modules = array(keys(set.new(loaded_modules):items())):sort(); - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing"; end -end -function send_to_online(message, server) + local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") + .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. + (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); + return { status = "completed", info = info }; +end); + +local function send_to_online(message, server) if server then sessions = { [server] = hosts[server] }; else @@ -633,202 +552,170 @@ function send_to_online(message, server) return c; end -function shut_down_service_handler(self, data, state) - local shut_down_service_layout = dataforms_new{ - title = "Shutting Down the Service"; - instructions = "Fill out this form to shut down the service."; - - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; - { name = "delay", type = "list-single", label = "Time delay before shutting down", - value = { {label = "30 seconds", value = "30"}, - {label = "60 seconds", value = "60"}, - {label = "90 seconds", value = "90"}, - {label = "2 minutes", value = "120"}, - {label = "3 minutes", value = "180"}, - {label = "4 minutes", value = "240"}, - {label = "5 minutes", value = "300"}, - }; +-- Shutting down the service +local shut_down_service_layout = dataforms_new{ + title = "Shutting Down the Service"; + instructions = "Fill out this form to shut down the service."; + + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; + { name = "delay", type = "list-single", label = "Time delay before shutting down", + value = { {label = "30 seconds", value = "30"}, + {label = "60 seconds", value = "60"}, + {label = "90 seconds", value = "90"}, + {label = "2 minutes", value = "120"}, + {label = "3 minutes", value = "180"}, + {label = "4 minutes", value = "240"}, + {label = "5 minutes", value = "300"}, }; - { name = "announcement", type = "text-multi", label = "Announcement" }; }; + { name = "announcement", type = "text-multi", label = "Announcement" }; +}; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end +local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err) + if err then + return generate_error_message(err); + end - local fields, err = shut_down_service_layout:data(data.form); + if fields.announcement and #fields.announcement > 0 then + local message = st.message({type = "headline"}, fields.announcement):up() + :tag("subject"):text("Server is shutting down"); + send_to_online(message); + end - if err then - return generate_error_message(err); - end + timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end); - if fields.announcement and #fields.announcement > 0 then - local message = st.message({type = "headline"}, fields.announcement):up() - :tag("subject"):text("Server is shutting down"); - send_to_online(message); - end + return { status = "completed", info = "Server is about to shut down" }; +end); - timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end); +-- Unloading modules +local unload_modules_layout = dataforms_new { + title = "Unload modules"; + instructions = "Select the modules to be unloaded"; - return { status = "completed", info = "Server is about to shut down" }; - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing"; - end -end - -function unload_modules_handler(self, data, state) - local layout = dataforms_new { - title = "Unload modules"; - instructions = "Select the modules to be unloaded"; + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" }; + { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"}; +}; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" }; - { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; +local unload_modules_handler = adhoc_initial(unload_modules_layout, function() + return { modules = array.collect(keys(hosts[module_host].modules)):sort() }; +end, function(fields, err) + if err then + return generate_error_message(err); + end + local ok_list, err_list = {}, {}; + for _, module in ipairs(fields.modules) do + local ok, err = modulemanager.unload(module_host, module); + if ok then + ok_list[#ok_list + 1] = module; + else + err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; end - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); + end + local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "") + .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. + (#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or ""); + return { status = "completed", info = info }; +end); + +-- Globally unloading a module +local globally_unload_module_layout = dataforms_new { + title = "Globally unload module"; + instructions = "Specify a module to unload on all hosts"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" }; + { name = "module", type = "list-single", required = true, label = "Module to globally unload:"}; +}; + +local globally_unload_module_handler = adhoc_initial(globally_unload_module_layout, function() + local loaded_modules = array(keys(modulemanager.get_modules("*"))); + for _, host in pairs(hosts) do + loaded_modules:append(array(keys(host.modules))); + end + loaded_modules = array(keys(set.new(loaded_modules):items())):sort(); + return { module = loaded_modules }; +end, function(fields, err) + local is_global = false; + if err then + return generate_error_message(err); + end + + if modulemanager.is_loaded("*", fields.module) then + local ok, err = modulemanager.unload("*", fields.module); + if not ok then + return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err }; end - local ok_list, err_list = {}, {}; - for _, module in ipairs(fields.modules) do - local ok, err = modulemanager.unload(data.to, module); + is_global = true; + end + + local ok_list, err_list = {}, {}; + for host_name, host in pairs(hosts) do + if modulemanager.is_loaded(host_name, fields.module) then + local ok, err = modulemanager.unload(host_name, fields.module); if ok then - ok_list[#ok_list + 1] = module; + ok_list[#ok_list + 1] = host_name; else - err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; + err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end - local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "") - .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. - (#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or ""); - return { status = "completed", info = info }; - else - local modules = array.collect(keys(hosts[data.to].modules)):sort(); - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing"; end -end - -local function globally_unload_module_handler(self, data, state) - local layout = dataforms_new { - title = "Globally unload module"; - instructions = "Specify a module to unload on all hosts"; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" }; - { name = "module", type = "list-single", required = true, label = "Module to globally unload:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; + if #ok_list == 0 and #err_list == 0 then + if is_global then + return { status = "completed", info = 'Successfully unloaded global module '..fields.module }; + else + return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; end + end - local is_global = false; - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end + local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") + .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. + (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); + return { status = "completed", info = info }; +end); - if modulemanager.is_loaded("*", fields.module) then - local ok, err = modulemanager.unload("*", fields.module); - if not ok then - return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err }; - end - is_global = true; - end +-- Activating a host +local activate_host_layout = dataforms_new { + title = "Activate host"; + instructions = ""; - local ok_list, err_list = {}, {}; - for host_name, host in pairs(hosts) do - if modulemanager.is_loaded(host_name, fields.module) then - local ok, err = modulemanager.unload(host_name, fields.module); - if ok then - ok_list[#ok_list + 1] = host_name; - else - err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; - end - end - end + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; + { name = "host", type = "text-single", required = true, label = "Host:"}; +}; - if #ok_list == 0 and #err_list == 0 then - if is_global then - return { status = "completed", info = 'Successfully unloaded global module '..fields.module }; - else - return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' }; - end - end +local activate_host_handler = adhoc_simple(activate_host_layout, function(fields, err) + if err then + return generate_error_message(err); + end + local ok, err = hostmanager_activate(fields.host); - local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "") - .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. - (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or ""); - return { status = "completed", info = info }; + if ok then + return { status = "completed", info = fields.host .. " activated" }; else - local loaded_modules = array(keys(modulemanager.get_modules("*"))); - for _, host in pairs(hosts) do - loaded_modules:append(array(keys(host.modules))); - end - loaded_modules = array(keys(set.new(loaded_modules):items())):sort(); - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing"; + return { status = "canceled", error = err } end -end +end); +-- Deactivating a host +local deactivate_host_layout = dataforms_new { + title = "Deactivate host"; + instructions = ""; -function activate_host_handler(self, data, state) - local layout = dataforms_new { - title = "Activate host"; - instructions = ""; + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; + { name = "host", type = "text-single", required = true, label = "Host:"}; +}; - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; - { name = "host", type = "text-single", required = true, label = "Host:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end - local ok, err = hostmanager_activate(fields.host); - - if ok then - return { status = "completed", info = fields.host .. " activated" }; - else - return { status = "canceled", error = err } - end - else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing"; +local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err) + if err then + return generate_error_message(err); end -end - -function deactivate_host_handler(self, data, state) - local layout = dataforms_new { - title = "Deactivate host"; - instructions = ""; + local ok, err = hostmanager_deactivate(fields.host); - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; - { name = "host", type = "text-single", required = true, label = "Host:"}; - }; - if state then - if data.action == "cancel" then - return { status = "canceled" }; - end - local fields, err = layout:data(data.form); - if err then - return generate_error_message(err); - end - local ok, err = hostmanager_deactivate(fields.host); - - if ok then - return { status = "completed", info = fields.host .. " deactivated" }; - else - return { status = "canceled", error = err } - end + if ok then + return { status = "completed", info = fields.host .. " deactivated" }; else - return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing"; + return { status = "canceled", error = err } end -end +end); local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin"); -- cgit v1.2.3 From a02d94ec2d18f73278d940d4d87a577d7ee9f28a Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Tue, 23 Apr 2013 14:41:52 -0400 Subject: util.json: Make encode(decode("[]"))=="[]". --- util/json.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/util/json.lua b/util/json.lua index ff7351a7..e8de4d2d 100644 --- a/util/json.lua +++ b/util/json.lua @@ -18,6 +18,9 @@ local error = error; local newproxy, getmetatable = newproxy, getmetatable; local print = print; +local has_array, array = pcall(require, "util.array"); +local array_mt = hasarray and getmetatable(array()) or {}; + --module("json") local json = {}; @@ -165,7 +168,12 @@ function simplesave(o, buffer) elseif t == "string" then stringsave(o, buffer); elseif t == "table" then - tablesave(o, buffer); + 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 @@ -237,7 +245,7 @@ function json.decode(json) local readvalue; local function readarray() - local t = {}; + local t = setmetatable({}, array_mt); next(); -- skip '[' skipstuff(); if ch == "]" then next(); return t; end -- cgit v1.2.3 From 5e16b34f1717a4e98425ef6186e0f96ec54f2ddb Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Tue, 23 Apr 2013 15:55:49 -0400 Subject: util.json: New, improved, fixed codepoint to UTF-8 conversion. --- util/json.lua | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/util/json.lua b/util/json.lua index e8de4d2d..9c2dd2c6 100644 --- a/util/json.lua +++ b/util/json.lua @@ -2,8 +2,6 @@ -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- --- utf8char copyright (C) 2007 Rici Lake --- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -41,32 +39,19 @@ for i=0,31 do if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end end -local function utf8char(i) - if i >= 0 then - i = i - i%1 - if i < 128 then - return s_char(i) - else - local c1 = i % 64 - i = (i - c1) / 64 - if i < 32 then - return s_char(0xC0+i, 0x80+c1) - else - local c2 = i % 64 - i = (i - c2) / 64 - if i < 16 and (i ~= 13 or c2 < 32) then - return s_char(0xE0+i, 0x80+c2, 0x80+c1) - elseif i >= 16 and i < 0x110 then - local c3 = i % 64 - i = (i - c3) / 64 - return s_char(0xF0+i, 0x80+c3, 0x80+c2, 0x80+c1) - end - 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, @@ -292,7 +277,7 @@ function json.decode(json) if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end seq = seq..ch; end - s = s..utf8char(tonumber(seq, 16)); + s = s..codepoint_to_utf8(tonumber(seq, 16)); next(); else error("invalid escape sequence in string"); end end -- cgit v1.2.3 From 685e47fc3b8c2c9e1598a32efc066f0a1aa1eeec Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Apr 2013 17:50:22 +0200 Subject: mod_c2s: Refactor building to allways tostring() it and only call send once --- plugins/mod_c2s.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index cafd0c71..efef8763 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -133,25 +133,25 @@ local function session_close(session, reason) session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); end if reason then -- nil == no err, initiated by us, false == initiated by client + local stream_error = st.stanza("stream:error"); if type(reason) == "string" then -- assume stream error - log("debug", "Disconnecting client, is: %s", reason); - session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); + stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif type(reason) == "table" then if reason.condition then - local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); + stream_error:tag(reason.condition, stream_xmlns_attr):up(); if reason.text then - stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); + stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then - stanza:add_child(reason.extra); + stream_error:add_child(reason.extra); end - log("debug", "Disconnecting client, is: %s", tostring(stanza)); - session.send(stanza); elseif reason.name then -- a stanza - log("debug", "Disconnecting client, is: %s", tostring(reason)); - session.send(reason); + stream_error = reason; end end + stream_error = tostring(stream_error); + log("debug", "Disconnecting client, is: %s", stream_error); + session.send(stream_error); end session.send(""); -- cgit v1.2.3 From 482b387173a4fbdec0a12e7eee778c1dd452eeb5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Apr 2013 20:36:55 +0100 Subject: MUC: add __tostring on room metatable --- plugins/muc/muc.lib.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index b6167a19..3af8c766 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -88,6 +88,10 @@ local function getText(stanza, path) return getUsingPath(stanza, path, true); en local room_mt = {}; room_mt.__index = room_mt; +function room_mt:__tostring() + return "MUC room ("..self.jid..")"; +end + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; -- cgit v1.2.3 From 34c8fe09b58f5deda6b6ce6232dad6b6c1f7d23c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Apr 2013 20:37:20 +0100 Subject: mod_admin_telnet: Add muc:create(room) (thanks SkyBlue) --- plugins/mod_admin_telnet.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index e1b90684..2622a5f9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -903,13 +903,23 @@ local console_room_mt = { end; }; -function def_env.muc:room(room_jid) - local room_name, host = jid_split(room_jid); +local function check_muc(jid) + local room_name, host = jid_split(jid); if not hosts[host] then return nil, "No such host: "..host; elseif not hosts[host].modules.muc then return nil, "Host '"..host.."' is not a MUC service"; end + return room_name, host; +end + +function def_env.muc:create(room_jid) + local room, host = check_muc(room_jid); + return hosts[host].modules.muc.create_room(room_jid); +end + +function def_env.muc:room(room_jid) + local room_name, host = check_muc(room_jid); local room_obj = hosts[host].modules.muc.rooms[room_jid]; if not room_obj then return nil, "No such room: "..room_jid; -- cgit v1.2.3 From 9204a89c5a035f82641c020741177a58331aba45 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 26 Apr 2013 12:25:25 +0100 Subject: mod_s2s: Obey tcp_keepalives option for s2s too, and make it individually configurable through s2s_tcp_keepalives (thanks yeled) --- plugins/mod_s2s/mod_s2s.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 0ece23a6..a935239e 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -33,7 +33,7 @@ local s2sout = module:require("s2sout"); local connect_timeout = module:get_option_number("s2s_timeout", 90); local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); - +local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day... local secure_domains, insecure_domains = module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items; @@ -563,6 +563,7 @@ local function initialize_session(session) end function listener.onconnect(conn) + conn:setoption("keepalive", opt_keepalives); local session = sessions[conn]; if not session then -- New incoming connection session = s2s_new_incoming(conn); -- cgit v1.2.3 From c2232bc15a95694f28440642cc7c9cf5f76fae7f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 27 Apr 2013 13:11:03 +0100 Subject: util.prosodyctl: Initialize storagemanager on the host before initializing usermanager. This fixes brokenness when the auth provider opens the store on load (as they all do since eeea0eb2602a) (thanks nulani) --- util/prosodyctl.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 1ab1d0bb..b80a69f2 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -140,11 +140,12 @@ function adduser(params) 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 - storagemanager.initialize_host(host); local ok, errmsg = usermanager.create_user(user, password, host); if not ok then @@ -155,11 +156,12 @@ end function user_exists(params) local user, host, password = nodeprep(params.user), nameprep(params.host), params.password; + + storagemanager.initialize_host(host); local provider = prosody.hosts[host].users; if not(provider) or provider.name == "null" then usermanager.initialize_host(host); end - storagemanager.initialize_host(host); return usermanager.user_exists(user, host); end -- cgit v1.2.3 From 75fb113a5bba95812e03c990d5e7df34226b4d31 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 27 Apr 2013 14:57:24 +0100 Subject: moduleapi: Add module:context(host) to produce a fake API context for a given host (or global). module:context("*"):get_option("foo") to get global options. --- core/moduleapi.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 30360f73..da44db5f 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -270,6 +270,10 @@ function api:get_option_set(name, ...) return set.new(value); end +function api:context(host) + return setmetatable({host=host or "*"}, {__index=self,__newindex=self}); +end + function api:add_item(key, value) self.items = self.items or {}; self.items[key] = self.items[key] or {}; -- cgit v1.2.3 From f61613d91a1c6b693d25e47774c5ff1fe3bf58ac Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 27 Apr 2013 14:59:00 +0100 Subject: moduleapi: Add module:get_option_inherited_set() to return a set that inherits items from a globally set set, if set --- core/moduleapi.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index da44db5f..9baf4fba 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -270,6 +270,18 @@ function api:get_option_set(name, ...) return set.new(value); end +function api:get_option_inherited_set(name, ...) + local value = self:get_option_set(name, ...); + local global_value = self:context("*"):get_option_set(name, ...); + if not value then + return global_value; + elseif not global_value then + return value; + end + value:include(global_value); + return value; +end + function api:context(host) return setmetatable({host=host or "*"}, {__index=self,__newindex=self}); end -- cgit v1.2.3 From 63f16cf5bb8fa4552a2d3035266ca3b339bc1655 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 27 Apr 2013 19:14:22 +0200 Subject: moduleapi: module:provides called without an item makes a copy of the environment instead. Fixes warnings about non-existent globals --- core/moduleapi.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 9baf4fba..764ed52a 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -319,7 +319,13 @@ function api:handle_items(type, added_cb, removed_cb, existing) end function api:provides(name, item) - if not item then item = self.environment; end + -- if not item then item = setmetatable({}, { __index = function(t,k) return rawget(self.environment, k); end }); end + if not item then + item = {} + for k,v in pairs(self.environment) do + if k ~= "module" then item[k] = v; end + end + end if not item.name then local item_name = self.name; -- Strip a provider prefix to find the item name -- cgit v1.2.3 From 3f516c749d334a0ad26a7b1b8111d0207a556e95 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 27 Apr 2013 19:44:37 +0200 Subject: moduleapi: in module:provides(), add the name of the module in item._provided_by --- core/moduleapi.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 764ed52a..ed75669b 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -335,6 +335,7 @@ function api:provides(name, item) end item.name = item_name; end + item._provided_by = self.name; self:add_item(name.."-provider", item); end -- cgit v1.2.3 From e5dc6ab94332c397431b40748ef0a486b8617e82 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 27 Apr 2013 22:46:01 +0200 Subject: net.server*: Allow the TCP backlog parameter to be set in the config --- net/server.lua | 1 + net/server_event.lua | 1 + net/server_select.lua | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/net/server.lua b/net/server.lua index ae3d45b0..375e7081 100644 --- a/net/server.lua +++ b/net/server.lua @@ -51,6 +51,7 @@ if prosody then if use_luaevent then local event_settings = { ACCEPT_DELAY = settings.event_accept_retry_interval; + ACCEPT_QUEUE = settings.tcp_backlog; CLEAR_DELAY = settings.event_clear_interval; CONNECT_TIMEOUT = settings.connect_timeout; DEBUG = settings.debug; diff --git a/net/server_event.lua b/net/server_event.lua index b34845d6..5eae95a9 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -23,6 +23,7 @@ local cfg = { HANDSHAKE_TIMEOUT = 60, -- timeout in seconds per handshake attempt MAX_READ_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes allowed to read from sockets MAX_SEND_LENGTH = 1024 * 1024 * 1024 * 1024, -- max bytes size of write buffer (for writing on sockets) + ACCEPT_QUEUE = 128, -- might influence the length of the pending sockets queue ACCEPT_DELAY = 10, -- seconds to wait until the next attempt of a full server to accept READ_TIMEOUT = 60 * 60 * 6, -- timeout in seconds for read data from socket WRITE_TIMEOUT = 180, -- timeout in seconds for write data on socket diff --git a/net/server_select.lua b/net/server_select.lua index 28f1dc6d..bdf262ae 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -101,6 +101,7 @@ local _readtraffic local _selecttimeout local _sleeptime +local _tcpbacklog local _starttime local _currenttime @@ -139,6 +140,7 @@ _readtraffic = 0 _selecttimeout = 1 -- timeout of socket.select _sleeptime = 0 -- time to wait at the end of every loop +_tcpbacklog = 128 -- some kind of hint to the OS _maxsendlen = 51000 * 1024 -- max len of send buffer _maxreadlen = 25000 * 1024 -- max len of read buffer @@ -211,7 +213,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- t handler.resume = function( ) if handler.paused then if not socket then - socket = socket_bind( ip, serverport ); + socket = socket_bind( ip, serverport, _tcpbacklog ); socket:settimeout( 0 ) end _readlistlen = addsocket(_readlist, socket, _readlistlen) @@ -720,7 +722,7 @@ addserver = function( addr, port, listeners, pattern, sslctx ) -- this function return nil, err end addr = addr or "*" - local server, err = socket_bind( addr, port ) + local server, err = socket_bind( addr, port, _tcpbacklog ) if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err @@ -772,6 +774,7 @@ getsettings = function( ) return { select_timeout = _selecttimeout; select_sleep_time = _sleeptime; + tcp_backlog = _tcpbacklog; max_send_buffer_size = _maxsendlen; max_receive_buffer_size = _maxreadlen; select_idle_check_interval = _checkinterval; @@ -792,6 +795,7 @@ changesettings = function( new ) _maxsendlen = tonumber( new.max_send_buffer_size ) or _maxsendlen _maxreadlen = tonumber( new.max_receive_buffer_size ) or _maxreadlen _checkinterval = tonumber( new.select_idle_check_interval ) or _checkinterval + _tcpbacklog = tonumber( new.tcp_backlog ) or _tcpbacklog _sendtimeout = tonumber( new.send_timeout ) or _sendtimeout _readtimeout = tonumber( new.read_timeout ) or _readtimeout _maxselectlen = new.max_connections or _maxselectlen -- cgit v1.2.3 From 8d7ce7626d5d7100b4b90933903ef6859d638926 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Apr 2013 16:22:01 +0200 Subject: prosodyctl: Put keys and certificates in ./certs when in a source checkout --- prosodyctl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prosodyctl b/prosodyctl index 24d28157..30a10b9a 100755 --- a/prosodyctl +++ b/prosodyctl @@ -654,7 +654,7 @@ end function cert_commands.config(arg) if #arg >= 1 and arg[1] ~= "--help" then - local conf_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cnf"; + local conf_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".cnf"; if ask_overwrite(conf_filename) then return nil, conf_filename; end @@ -687,7 +687,7 @@ end function cert_commands.key(arg) if #arg >= 1 and arg[1] ~= "--help" then - local key_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".key"; + local key_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".key"; if ask_overwrite(key_filename) then return nil, key_filename; end @@ -709,7 +709,7 @@ end function cert_commands.request(arg) if #arg >= 1 and arg[1] ~= "--help" then - local req_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".req"; + local req_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".req"; if ask_overwrite(req_filename) then return nil, req_filename; end @@ -727,7 +727,7 @@ end function cert_commands.generate(arg) if #arg >= 1 and arg[1] ~= "--help" then - local cert_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".crt"; + local cert_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".crt"; if ask_overwrite(cert_filename) then return nil, cert_filename; end -- cgit v1.2.3 From 4a4f57907c6dacaf1c002e2c6e730a3853fbf05e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 29 Apr 2013 00:33:39 +0100 Subject: mod_s2s: Ensure that to/from on stream headers are always correct, fixes #338 --- plugins/mod_s2s/mod_s2s.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index a935239e..30ebb706 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -348,7 +348,7 @@ function stream_callbacks.streamopened(session, attr) end end - session:open_stream() + session:open_stream(session.to_host, session.from_host) if session.version >= 1.0 then local features = st.stanza("stream:features"); @@ -448,7 +448,11 @@ local function session_close(session, reason, remote_reason) local log = session.log or log; if session.conn then if session.notopen then - session:open_stream() + if session.direction == "incoming" then + session:open_stream(session.to_host, session.from_host); + else + session:open_stream(session.from_host, session.to_host); + end end if reason then -- nil == no err, initiated by us, false == initiated by remote if type(reason) == "string" then -- assume stream error @@ -496,8 +500,6 @@ local function session_close(session, reason, remote_reason) end function session_open_stream(session, from, to) - local from = from or session.from_host; - local to = to or session.to_host; local attr = { ["xmlns:stream"] = 'http://etherx.jabber.org/streams', xmlns = 'jabber:server', @@ -506,8 +508,7 @@ function session_open_stream(session, from, to) id = session.streamid, from = from, to = to, } - local local_host = session.direction == "outgoing" and from or to; - if not local_host or (hosts[local_host] and hosts[local_host].modules.dialback) then + if not from or (hosts[from] and hosts[from].modules.dialback) then attr["xmlns:db"] = 'jabber:server:dialback'; end -- cgit v1.2.3 From 590cf9abf5b8630a09cd1ede9c025f60440fbabf Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 29 Apr 2013 10:43:44 +0100 Subject: mod_saslauth, mod_compression: Fix some cases where open_stream() was not being passed to/from (see df3c78221f26 and issue #338) --- plugins/mod_compression.lua | 2 +- plugins/mod_saslauth.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua index 44bc05fe..92856099 100644 --- a/plugins/mod_compression.lua +++ b/plugins/mod_compression.lua @@ -141,7 +141,7 @@ module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(ev -- setup decompression for session.data setup_decompression(session, inflate_stream); session:reset_stream(); - session:open_stream(); + session:open_stream(session.from_host, session.to_host); session.compressed = true; return true; end diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index b75b1844..201cc477 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -87,7 +87,7 @@ module:hook_stanza(xmlns_sasl, "success", function (session, stanza) module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host); session.external_auth = "succeeded" session:reset_stream(); - session:open_stream(); + session:open_stream(session.from_host, session.to_host); module:fire_event("s2s-authenticated", { session = session, host = session.to_host }); return true; -- cgit v1.2.3