Changeset

7036:f26debcae34e

Merge 0.10->trunk
author Kim Alvefur <zash@zash.se>
date Sun, 27 Dec 2015 12:29:28 +0100
parents 7015:17e275e8bd79 (current diff) 7035:085a286e2873 (diff)
children 7038:22f36a3d6369
files plugins/mod_admin_telnet.lua
diffstat 11 files changed, 452 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/certs/Makefile	Wed Dec 23 11:43:39 2015 +0100
+++ b/certs/Makefile	Sun Dec 27 12:29:28 2015 +0100
@@ -15,16 +15,48 @@
 
 # To request a cert
 %.csr: %.cnf %.key
-	openssl req -new -key $(lastword $^) -out $@ -utf8 -config $(firstword $^)
+	openssl req -new -key $(lastword $^) \
+		-sha256 -utf8 -config $(firstword $^) -out $@
+
+%.csr: %.cnf
+	umask 0077 && touch $*.key
+	openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+		-sha256 -utf8 -config $^ -out $@
+	@chmod 400 $*.key -c
+
+%.csr: %.key
+	openssl req -new -key $^ -utf8 -subj /CN=$* -out $@
+
+%.csr:
+	umask 0077 && touch $*.key
+	openssl req -new -newkey rsa:$(keysize) -nodes -keyout $*.key \
+		-utf8 -subj /CN=$* -out $@
+	@chmod 400 $*.key -c
 
 # Self signed
 %.crt: %.cnf %.key
-	openssl req -new -x509 -nodes -key $(lastword $^) -days 365 \
-		-sha1 -out $@ -utf8 -config $(firstword $^)
+	openssl req -new -x509 -key $(lastword $^) -days 365 -sha256 -utf8 \
+		-config $(firstword $^) -out $@
+
+%.crt: %.cnf
+	umask 0077 && touch $*.key
+	openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+		-days 365 -sha256 -utf8 -config $(firstword $^) -out $@
+	@chmod 400 $*.key -c
 
+%.crt: %.key
+	openssl req -new -x509 -key $^ -days 365 -sha256 -utf8 -subj /CN=$* -out $@
+
+%.crt:
+	umask 0077 && touch $*.key
+	openssl req -new -x509 -newkey rsa:$(keysize) -nodes -keyout $*.key \
+		-days 365 -sha256 -out $@ -utf8 -subj /CN=$*
+	@chmod 400 $*.key -c
+
+# Generate a config from the example
 %.cnf:
 	sed 's,example\.com,$*,g' openssl.cnf > $@
 
 %.key:
-	openssl genrsa $(keysize) > $@
-	@chmod 400 $@
+	umask 0077 && openssl genrsa -out $@ $(keysize)
+	@chmod 400 $@ -c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/man/Makefile	Sun Dec 27 12:29:28 2015 +0100
@@ -0,0 +1,4 @@
+all: prosodyctl.man
+
+%.man: %.markdown
+	pandoc -s -t man -o $@ $^
--- a/man/prosodyctl.man	Wed Dec 23 11:43:39 2015 +0100
+++ b/man/prosodyctl.man	Sun Dec 27 12:29:28 2015 +0100
@@ -1,83 +1,140 @@
-.TH PROSODYCTL 1 "2009-07-02"
-
+.\" Automatically generated by Pandoc 1.15.2
+.\"
+.hy
+.TH "PROSODYCTL" "1" "2015\-12\-23" "" ""
 .SH NAME
+.PP
 prosodyctl \- Manage a Prosody XMPP server
-
 .SH SYNOPSIS
-\fBprosodyctl\fP \fIcommand\fP [\fI--help\fP]
-
+.IP
+.nf
+\f[C]
+prosodyctl\ command\ [\-\-help]
+\f[]
+.fi
 .SH DESCRIPTION
-\fBprosodyctl\fP is the control tool for the Prosody XMPP server. It may be
-used to control the server daemon and manage users.
-
-\fBprosodyctl\fP needs to be executed with sufficient privileges to perform
-its commands. This typically means executing \fBprosodyctl\fP as the root user.
-If a user named "prosody" is found then \fBprosodyctl\fP will change to that
+.PP
+prosodyctl is the control tool for the Prosody XMPP server.
+It may be used to control the server daemon and manage users.
+.PP
+prosodyctl needs to be executed with sufficient privileges to perform
+its commands.
+This typically means executing prosodyctl as the root user.
+If a user named "prosody" is found then prosodyctl will change to that
 user before executing its commands.
-
 .SH COMMANDS
 .SS User Management
-In the following commands users are identified by a Jabber ID, \fIjid\fP, of the
-usual form: user@domain.
-
-.IP "\fBadduser\fP \fIjid\fP"
-Adds a user with Jabber ID, \fIjid\fP, to the server. You will be
-prompted to enter the user's password.
-
-.IP "\fBpasswd\fP \fIjid\fP"
-Changes the password of an existing user with Jabber ID, \fIjid\fP. You will be
-prompted to enter the user's new password.
-
-.IP "\fBdeluser\fP \fIjid\fP"
-Deletes an existing user with Jabber ID, \fIjid\fP, from the server.
-
+.PP
+In the following commands users are identified by a Jabber ID, jid, of
+the usual form: user\@domain.
+.TP
+.B adduser jid
+Adds a user with Jabber ID, jid, to the server.
+You will be prompted to enter the user\[aq]s password.
+.RS
+.RE
+.TP
+.B passwd jid
+Changes the password of an existing user with Jabber ID, jid.
+You will be prompted to enter the user\[aq]s new password.
+.RS
+.RE
+.TP
+.B deluser jid
+Deletes an existing user with Jabber ID, jid, from the server.
+.RS
+.RE
 .SS Daemon Management
-Although \fBprosodyctl\fP has commands to manage the \fBprosody\fP daemon it is
-recommended that you utilize your distributions daemon management features if
-you attained Prosody through a package.
-
-To perform daemon control commands \fBprosodyctl\fP needs a \fIpidfile\fP value
-specified in \fI/etc/prosody/prosody.cfg.lua\fP. Failure to do so will cause
-\fBprosodyctl\fP to complain.
-
-.IP \fBstart\fP
-Starts the \fBprosody\fP server daemon. If run as root \fBprosodyctl\fP will
-attempt to change to a user named "prosody" before executing. This operation
-will block for up to five seconds to wait for the server to execute.
-
-.IP \fBstop\fP
-Stops the \fBprosody\fP server daemon. This operation will block for up to five
-seconds to wait for the server to stop executing.
+.PP
+Although prosodyctl has commands to manage the prosody daemon it is
+recommended that you utilize your distributions daemon management
+features if you attained Prosody through a package.
+.PP
+To perform daemon control commands prosodyctl needs a pidfile value
+specified in \f[C]/etc/prosody/prosody.cfg.lua\f[].
+Failure to do so will cause prosodyctl to complain.
+.TP
+.B start
+Starts the prosody server daemon.
+If run as root prosodyctl will attempt to change to a user named
+"prosody" before executing.
+This operation will block for up to five seconds to wait for the server
+to execute.
+.RS
+.RE
+.TP
+.B stop
+Stops the prosody server daemon.
+This operation will block for up to five seconds to wait for the server
+to stop executing.
+.RS
+.RE
+.TP
+.B restart
+Restarts the prosody server daemon.
+Equivalent to running prosodyctl stop followed by prosodyctl start.
+.RS
+.RE
+.TP
+.B reload
+Signals the prosody server daemon to reload configuration and reopen log
+files.
+.RS
+.RE
+.TP
+.B status
+Prints the current execution status of the prosody server daemon.
+.RS
+.RE
+.SS Debugging
+.PP
+prosodyctl can also show some information about the environment,
+dependencies and such to aid in debugging.
+.TP
+.B about
+Shows environment, various paths used by Prosody and installed
+dependencies.
+.RS
+.RE
+.TP
+.B check [what]
+Performs various sanity checks on the configuration, DNS setup and
+configured TLS certificates.
+\f[C]what\f[] can be one of \f[C]config\f[], \f[C]dns\f[] and
+\f[C]certs\f[] to run only that check.
+.RS
+.RE
+.SS Ejabberd Compatibility
+.PP
+ejabberd is another XMPP server which provides a comparable control
+tool, ejabberdctl, to control its server\[aq]s operations.
+prosodyctl implements some commands which are compatible with
+ejabberdctl.
+For details of how these commands work you should see ejabberdctl(8).
+.IP
+.nf
+\f[C]
+register\ user\ server\ password
 
-.IP \fBrestart\fP
-Restarts the \fBprosody\fP server daemon. Equivalent to running \fBprosodyctl
-stop\fP followed by \fBprosodyctl start\fP.
-
-.IP \fBstatus\fP
-Prints the current execution status of the \fBprosody\fP server daemon.
-
-.SS Ejabberd Compatibility
-\fBejabberd\fP is another XMPP server which provides a comparable control tool,
-\fBejabberdctl\fP, to control its server's operations. \fBprosodyctl\fP
-implements some commands which are compatible with \fBejabberdctl\fP. For
-details of how these commands work you should see
-.BR ejabberdctl (8).
-
-.IP "\fBregister\fP \fIuser server password\fP"
-.IP "\fBunregister\fP \fIuser server\fP"
-
+unregister\ user\ server
+\f[]
+.fi
 .SH OPTIONS
-.IP \fI--help\fP
+.TP
+.B \f[C]\-\-help\f[]
 Display help text for the specified command.
-
+.RS
+.RE
 .SH FILES
-.IP \fI/etc/prosody/prosody.cfg.lua\fP
-The main \fBprosody\fP configuration file. \fBprosodyctl\fP reads this to
-determine the process ID file of the \fBprosody\fP server daemon and to
-determine if a host has been configured.
-
+.TP
+.B \f[C]/etc/prosody/prosody.cfg.lua\f[]
+The main prosody configuration file.
+prosodyctl reads this to determine the process ID file of the prosody
+server daemon and to determine if a host has been configured.
+.RS
+.RE
 .SH ONLINE
-More information may be found online at: \fIhttp://prosody.im/\fP
-
+.PP
+More information may be found online at: <https://prosody.im/>
 .SH AUTHORS
-Dwayne Bent <dbb.1@liqd.org>
+Dwayne Bent <dbb.1@liqd.org>; Kim Alvefur.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/man/prosodyctl.markdown	Sun Dec 27 12:29:28 2015 +0100
@@ -0,0 +1,127 @@
+---
+author:
+- 'Dwayne Bent <dbb.1@liqd.org>'
+- Kim Alvefur
+date: '2015-12-23'
+section: 1
+title: PROSODYCTL
+...
+
+NAME
+====
+
+prosodyctl - Manage a Prosody XMPP server
+
+SYNOPSIS
+========
+
+    prosodyctl command [--help]
+
+DESCRIPTION
+===========
+
+prosodyctl is the control tool for the Prosody XMPP server. It may be
+used to control the server daemon and manage users.
+
+prosodyctl needs to be executed with sufficient privileges to perform
+its commands. This typically means executing prosodyctl as the root
+user. If a user named "prosody" is found then prosodyctl will change to
+that user before executing its commands.
+
+COMMANDS
+========
+
+User Management
+---------------
+
+In the following commands users are identified by a Jabber ID, jid, of
+the usual form: user@domain.
+
+adduser jid
+:   Adds a user with Jabber ID, jid, to the server. You will be prompted
+    to enter the user's password.
+
+passwd jid
+:   Changes the password of an existing user with Jabber ID, jid. You
+    will be prompted to enter the user's new password.
+
+deluser jid
+:   Deletes an existing user with Jabber ID, jid, from the server.
+
+Daemon Management
+-----------------
+
+Although prosodyctl has commands to manage the prosody daemon it is
+recommended that you utilize your distributions daemon management
+features if you attained Prosody through a package.
+
+To perform daemon control commands prosodyctl needs a pidfile value
+specified in `/etc/prosody/prosody.cfg.lua`. Failure to do so will cause
+prosodyctl to complain.
+
+start
+:   Starts the prosody server daemon. If run as root prosodyctl will
+    attempt to change to a user named "prosody" before executing. This
+    operation will block for up to five seconds to wait for the server
+    to execute.
+
+stop
+:   Stops the prosody server daemon. This operation will block for up to
+    five seconds to wait for the server to stop executing.
+
+restart
+:   Restarts the prosody server daemon. Equivalent to running prosodyctl
+    stop followed by prosodyctl start.
+
+reload
+:   Signals the prosody server daemon to reload configuration and reopen
+    log files.
+
+status
+:   Prints the current execution status of the prosody server daemon.
+
+Debugging
+---------
+
+prosodyctl can also show some information about the environment,
+dependencies and such to aid in debugging.
+
+about
+:   Shows environment, various paths used by Prosody and
+    installed dependencies.
+
+check \[what\]
+:   Performs various sanity checks on the configuration, DNS setup and
+    configured TLS certificates. `what` can be one of `config`, `dns`
+    and `certs` to run only that check.
+
+Ejabberd Compatibility
+----------------------
+
+ejabberd is another XMPP server which provides a comparable control
+tool, ejabberdctl, to control its server's operations. prosodyctl
+implements some commands which are compatible with ejabberdctl. For
+details of how these commands work you should see ejabberdctl(8).
+
+    register user server password
+
+    unregister user server
+
+OPTIONS
+=======
+
+`--help`
+:   Display help text for the specified command.
+
+FILES
+=====
+
+`/etc/prosody/prosody.cfg.lua`
+:   The main prosody configuration file. prosodyctl reads this to
+    determine the process ID file of the prosody server daemon and to
+    determine if a host has been configured.
+
+ONLINE
+======
+
+More information may be found online at: <https://prosody.im/>
--- a/plugins/mod_register.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/plugins/mod_register.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -13,9 +13,10 @@
 local usermanager_create_user = require "core.usermanager".create_user;
 local usermanager_set_password = require "core.usermanager".set_password;
 local usermanager_delete_user = require "core.usermanager".delete_user;
-local os_time = os.time;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 local jid_bare = require "util.jid".bare;
+local create_throttle = require "util.throttle".create;
+local new_cache = require "util.cache".new;
 
 local compat = module:get_option_boolean("registration_compat", true);
 local allow_registration = module:get_option_boolean("allow_registration", false);
@@ -84,6 +85,7 @@
 
 local function handle_registration_stanza(event)
 	local session, stanza = event.origin, event.stanza;
+	local log = session.log or module._log;
 
 	local query = stanza.tags[1];
 	if stanza.attr.type == "get" then
@@ -97,6 +99,7 @@
 		if query.tags[1] and query.tags[1].name == "remove" then
 			local username, host = session.username, session.host;
 
+			-- This one weird trick sends a reply to this stanza before the user is deleted
 			local old_session_close = session.close;
 			session.close = function(session, ...)
 				session.send(st.reply(stanza));
@@ -106,13 +109,13 @@
 			local ok, err = usermanager_delete_user(username, host);
 
 			if not ok then
-				module:log("debug", "Removing user account %s@%s failed: %s", username, host, err);
+				log("debug", "Removing user account %s@%s failed: %s", username, host, err);
 				session.close = old_session_close;
 				session.send(st.error_reply(stanza, "cancel", "service-unavailable", err));
 				return true;
 			end
 
-			module:log("info", "User removed their account: %s@%s", username, host);
+			log("info", "User removed their account: %s@%s", username, host);
 			module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
 		else
 			local username = nodeprep(query:get_child_text("username"));
@@ -169,14 +172,36 @@
 	end
 end
 
-local recent_ips = {};
 local min_seconds_between_registrations = module:get_option_number("min_seconds_between_registrations");
 local whitelist_only = module:get_option_boolean("whitelist_registration_only");
 local whitelisted_ips = module:get_option_set("registration_whitelist", { "127.0.0.1" })._items;
 local blacklisted_ips = module:get_option_set("registration_blacklist", {})._items;
 
+local throttle_max = module:get_option_number("registration_throttle_max", min_seconds_between_registrations and 1);
+local throttle_period = module:get_option_number("registration_throttle_period", min_seconds_between_registrations);
+local throttle_cache_size = module:get_option_number("registration_throttle_cache_size", 100);
+local blacklist_overflow = module_get_option_boolean("blacklist_on_registration_throttle_overload", false);
+
+local throttle_cache = new_cache(throttle_cache_size, blacklist_overflow and function (ip, throttle)
+	if not throttle:peek() then
+		module:log("info", "Adding ip %s to registration blacklist", ip);
+		blacklisted_ips[ip] = true;
+	end
+end);
+
+local function check_throttle(ip)
+	if not throttle_max then return true end
+	local throttle = throttle_cache:get(ip);
+	if not throttle then
+		throttle = create_throttle(throttle_max, throttle_period);
+	end
+	throttle_cache:set(ip, throttle);
+	return throttle:poll(1);
+end
+
 module:hook("stanza/iq/jabber:iq:register:query", function(event)
 	local session, stanza = event.origin, event.stanza;
+	local log = session.log or module._log;
 
 	if not(allow_registration) or session.type ~= "c2s_unauthed" then
 		session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
@@ -196,23 +221,14 @@
 				else
 					-- Check that the user is not blacklisted or registering too often
 					if not session.ip then
-						module:log("debug", "User's IP not known; can't apply blacklist/whitelist");
+						log("debug", "User's IP not known; can't apply blacklist/whitelist");
 					elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then
 						session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account."));
 						return true;
 					elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then
-						if not recent_ips[session.ip] then
-							recent_ips[session.ip] = { time = os_time(), count = 1 };
-						else
-							local ip = recent_ips[session.ip];
-							ip.count = ip.count + 1;
-
-							if os_time() - ip.time < min_seconds_between_registrations then
-								ip.time = os_time();
-								session.send(st.error_reply(stanza, "wait", "not-acceptable"));
-								return true;
-							end
-							ip.time = os_time();
+						if check_throttle(session.ip) then
+							session.send(st.error_reply(stanza, "wait", "not-acceptable"));
+							return true;
 						end
 					end
 					local username, password = nodeprep(data.username), data.password;
@@ -238,7 +254,7 @@
 								return true;
 							end
 							session.send(st.reply(stanza)); -- user created!
-							module:log("info", "User account created: %s@%s", username, host);
+							log("info", "User account created: %s@%s", username, host);
 							module:fire_event("user-registered", {
 								username = username, host = host, source = "mod_register",
 								session = session });
--- a/tests/test.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/tests/test.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -21,6 +21,7 @@
 	dotest "util.stanza"
 	dotest "util.sasl.scram"
 	dotest "util.cache"
+	dotest "util.throttle"
 
 	dosingletest("test_sasl.lua", "latin1toutf8");
 	dosingletest("test_utf8.lua", "valid");
--- a/tests/test_util_cache.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/tests/test_util_cache.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -170,5 +170,60 @@
 	end
 	assert_equal(i, 4);
 	
+	local evicted_key, evicted_value;
+	local c = new(3, function (_key, _value)
+		evicted_key, evicted_value = _key, _value;
+	end);
+	local function set(k, v, should_evict_key, should_evict_value)
+		evicted_key, evicted_value = nil, nil;
+		c:set(k, v);
+		assert_equal(evicted_key, should_evict_key);
+		assert_equal(evicted_value, should_evict_value);
+	end
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
 
+	set("b", 2)
+	set("c", 3)
+	set("b", 2)
+	set("d", 4, "a", 1)
+	set("e", 5, "c", 3)
+	
+
+	local evicted_key, evicted_value;
+	local c3 = new(1, function (_key, _value, c3)
+		evicted_key, evicted_value = _key, _value;
+		if _key == "a" then
+			-- Put it back in...
+			-- Check that the newest key/value was set before on_evict was called
+			assert_equal(c3:get("b"), 2);
+			-- Sanity check for what we're evicting
+			assert_equal(_key, "a");
+			assert_equal(_value, 1);
+			-- Re-insert the evicted key (causes this evict function to run again with "b",2)
+			c3:set(_key, _value)
+			assert_equal(c3:get(_key), _value)
+		end
+	end);
+	local function set(k, v, should_evict_key, should_evict_value)
+		evicted_key, evicted_value = nil, nil;
+		c3:set(k, v);
+		assert_equal(evicted_key, should_evict_key);
+		assert_equal(evicted_value, should_evict_value);
+	end
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
+	set("a", 1)
+
+	-- The evict handler re-inserts "a"->1, so "b" gets evicted:
+	set("b", 2, "b", 2)
+	-- Check the final state is what we expect
+	assert_equal(c3:get("a"), 1);
+	assert_equal(c3:get("b"), nil);
+	assert_equal(c3:count(), 1);
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_util_throttle.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -0,0 +1,34 @@
+
+local now = 0; -- wibbly-wobbly... timey-wimey... stuff
+local function predictable_gettime()
+	return now;
+end
+local function later(n)
+	now = now + n; -- time passes at a different rate
+end
+
+local function override_gettime(throttle)
+	local i = 0;
+	repeat
+		i = i + 1;
+		local name = debug.getupvalue(throttle.update, i);
+		if name then
+			debug.setupvalue(throttle.update, i, predictable_gettime);
+			return throttle;
+		end
+	until not name;
+end
+
+function create(create)
+	local a = override_gettime( create(3, 10) );
+
+	assert_equal(a:poll(1), true);  -- 3 -> 2
+	assert_equal(a:poll(1), true);  -- 2 -> 1
+	assert_equal(a:poll(1), true);  -- 1 -> 0
+	assert_equal(a:poll(1), false); -- MEEP, out of credits!
+	later(1);                       -- ... what about
+	assert_equal(a:poll(1), false); -- now? - Still no!
+	later(9);                       -- Later that day
+	assert_equal(a:poll(1), true);  -- Should be back at 3 credits ... 2
+end
+
--- a/util/array.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/util/array.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -37,7 +37,7 @@
 
 -- Read-only methods
 function array_methods:random()
-	return self[math_random(1,#self)];
+	return self[math_random(1, #self)];
 end
 
 -- These methods can be called two ways:
@@ -45,7 +45,7 @@
 --   existing_array:method([params, ...]) -- Transform existing array into result
 --
 function array_base.map(outa, ina, func)
-	for k,v in ipairs(ina) do
+	for k, v in ipairs(ina) do
 		outa[k] = func(v);
 	end
 	return outa;
@@ -54,7 +54,7 @@
 function array_base.filter(outa, ina, func)
 	local inplace, start_length = ina == outa, #ina;
 	local write = 1;
-	for read=1,start_length do
+	for read = 1, start_length do
 		local v = ina[read];
 		if func(v) then
 			outa[write] = v;
@@ -63,7 +63,7 @@
 	end
 
 	if inplace and write <= start_length then
-		for i=write,start_length do
+		for i = write, start_length do
 			outa[i] = nil;
 		end
 	end
@@ -80,7 +80,7 @@
 end
 
 function array_base.pluck(outa, ina, key)
-	for i=1,#ina do
+	for i = 1, #ina do
 		outa[i] = ina[i][key];
 	end
 	return outa;
@@ -108,16 +108,16 @@
 --- These methods only mutate the array
 function array_methods:shuffle(outa, ina)
 	local len = #self;
-	for i=1,#self do
-		local r = math_random(i,len);
+	for i = 1, #self do
+		local r = math_random(i, len);
 		self[i], self[r] = self[r], self[i];
 	end
 	return self;
 end
 
 function array_methods:append(array)
-	local len,len2  = #self, #array;
-	for i=1,len2 do
+	local len, len2 = #self, #array;
+	for i = 1, len2 do
 		self[len+i] = array[i];
 	end
 	return self;
@@ -128,11 +128,7 @@
 	return self;
 end
 
-function array_methods:pop(x)
-	local v = self[x];
-	t_remove(self, x);
-	return v;
-end
+array_methods.pop = t_remove;
 
 function array_methods:concat(sep)
 	return t_concat(array.map(self, tostring), sep);
@@ -147,7 +143,7 @@
 	local t = {};
 	while true do
 		var = f(s, var);
-	        if var == nil then break; end
+		if var == nil then break; end
 		t_insert(t, var);
 	end
 	return setmetatable(t, array_mt);
--- a/util/cache.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/util/cache.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -51,19 +51,20 @@
 		return true;
 	end
 	-- Check whether we need to remove oldest k/v
+	local on_evict, evicted_key, evicted_value;
 	if self._count == self.size then
 		local tail = self._tail;
-		local on_evict = self._on_evict;
-		if on_evict then
-			on_evict(tail.key, tail.value);
-		end
+		on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
 		_remove(self, tail);
-		self._data[tail.key] = nil;
+		self._data[evicted_key] = nil;
 	end
 
 	m = { key = k, value = v, prev = nil, next = nil };
 	self._data[k] = m;
 	_insert(self, m);
+	if on_evict and evicted_key then
+		on_evict(evicted_key, evicted_value, self);
+	end
 	return true;
 end
 
--- a/util/openssl.lua	Wed Dec 23 11:43:39 2015 +0100
+++ b/util/openssl.lua	Sun Dec 27 12:29:28 2015 +0100
@@ -12,7 +12,7 @@
 _M.config = config;
 
 local ssl_config = {};
-local ssl_config_mt = {__index=ssl_config};
+local ssl_config_mt = { __index = ssl_config };
 
 function config.new()
 	return setmetatable({
@@ -65,12 +65,12 @@
 		s = s .. ("[%s]\n"):format(k);
 		if k == "subject_alternative_name" then
 			for san, n in pairs(t) do
-				for i = 1,#n do
+				for i = 1, #n do
 					s = s .. s_format("%s.%d = %s\n", san, i -1, n[i]);
 				end
 			end
 		elseif k == "distinguished_name" then
-			for i=1,#DN_order do
+			for i=1, #DN_order do
 				local k = DN_order[i]
 				local v = t[k];
 				if v then
@@ -107,7 +107,7 @@
 
 function ssl_config:add_sRVName(host, service)
 	t_insert(self.subject_alternative_name.otherName,
-		s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .."." .. idna_to_ascii(host))));
+		s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .. "." .. idna_to_ascii(host))));
 end
 
 function ssl_config:add_xmppAddr(host)
@@ -118,10 +118,10 @@
 function ssl_config:from_prosody(hosts, config, certhosts)
 	-- TODO Decide if this should go elsewhere
 	local found_matching_hosts = false;
-	for i = 1,#certhosts do
+	for i = 1, #certhosts do
 		local certhost = certhosts[i];
 		for name in pairs(hosts) do
-			if name == certhost or name:sub(-1-#certhost) == "."..certhost then
+			if name == certhost or name:sub(-1-#certhost) == "." .. certhost then
 				found_matching_hosts = true;
 				self:add_dNSName(name);
 				--print(name .. "#component_module: " .. (config.get(name, "component_module") or "nil"));
@@ -144,30 +144,30 @@
 
 do -- Lua to shell calls.
 	local function shell_escape(s)
-		return s:gsub("'",[['\'']]);
+		return "'" .. tostring(s):gsub("'",[['\'']]) .. "'";
 	end
 
-	local function serialize(f,o)
-		local r = {"openssl", f};
-		for k,v in pairs(o) do
+	local function serialize(command, args)
+		local commandline = { "openssl", command };
+		for k, v in pairs(args) do
 			if type(k) == "string" then
-				t_insert(r, ("-%s"):format(k));
+				t_insert(commandline, ("-%s"):format(k));
 				if v ~= true then
-					t_insert(r, ("'%s'"):format(shell_escape(tostring(v))));
+					t_insert(commandline, shell_escape(v));
 				end
 			end
 		end
-		for _,v in ipairs(o) do
-			t_insert(r, ("'%s'"):format(shell_escape(tostring(v))));
+		for _, v in ipairs(args) do
+			t_insert(commandline, shell_escape(v));
 		end
-		return t_concat(r, " ");
+		return t_concat(commandline, " ");
 	end
 
 	local os_execute = os.execute;
 	setmetatable(_M, {
-		__index=function(_,f)
+		__index = function(_, command)
 			return function(opts)
-				return 0 == os_execute(serialize(f, type(opts) == "table" and opts or {}));
+				return 0 == os_execute(serialize(command, type(opts) == "table" and opts or {}));
 			end;
 		end;
 	});