Changeset

6233:f400a4cdf352

Merge with daurnimator
author Matthew Wild <mwild1@gmail.com>
date Sat, 17 May 2014 18:17:34 +0100
parents 6178:e12b13a46878 (diff) 6232:d7dc71d9171d (current diff)
children 6254:da4c04df90e3
files plugins/muc/muc.lib.lua
diffstat 14 files changed, 262 insertions(+), 120 deletions(-) [+]
line wrap: on
line diff
--- a/core/certmanager.lua	Thu May 08 18:09:59 2014 +0100
+++ b/core/certmanager.lua	Sat May 17 18:17:34 2014 +0100
@@ -19,7 +19,7 @@
 local t_insert = table.insert;
 
 local prosody = prosody;
-local resolve_path = configmanager.resolve_relative_path;
+local resolve_path = require"util.paths".resolve_relative_path;
 local config_path = prosody.paths.config;
 
 local luasec_has_noticket, luasec_has_verifyext, luasec_has_no_compression;
--- a/core/configmanager.lua	Thu May 08 18:09:59 2014 +0100
+++ b/core/configmanager.lua	Sat May 17 18:17:34 2014 +0100
@@ -14,11 +14,15 @@
 local fire_event = prosody and prosody.events.fire_event or function () end;
 
 local envload = require"util.envload".envload;
-local lfs = require "lfs";
+local deps = require"util.dependencies";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
+local glob_to_pattern = require"util.paths".glob_to_pattern;
 local path_sep = package.config:sub(1,1);
 
 module "configmanager"
 
+_M.resolve_relative_path = resolve_relative_path; -- COMPAT
+
 local parsers = {};
 
 local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
@@ -66,41 +70,6 @@
 	return set(config, host, key, value);
 end
 
--- Helper function to resolve relative paths (needed by config)
-do
-	function resolve_relative_path(parent_path, path)
-		if path then
-			-- Some normalization
-			parent_path = parent_path:gsub("%"..path_sep.."+$", "");
-			path = path:gsub("^%.%"..path_sep.."+", "");
-
-			local is_relative;
-			if path_sep == "/" and path:sub(1,1) ~= "/" then
-				is_relative = true;
-			elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
-				is_relative = true;
-			end
-			if is_relative then
-				return parent_path..path_sep..path;
-			end
-		end
-		return path;
-	end
-end
-
--- Helper function to convert a glob to a Lua pattern
-local function glob_to_pattern(glob)
-	return "^"..glob:gsub("[%p*?]", function (c)
-		if c == "*" then
-			return ".*";
-		elseif c == "?" then
-			return ".";
-		else
-			return "%"..c;
-		end
-	end).."$";
-end
-
 function load(filename, format)
 	format = format or filename:match("%w+$");
 
@@ -214,6 +183,10 @@
 
 		function env.Include(file)
 			if file:match("[*?]") then
+				local lfs = deps.softreq "lfs";
+				if not lfs then
+					error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file));
+				end
 				local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
 				local path = file:sub(1, math_max(path_pos-2,0));
 				local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
--- a/core/moduleapi.lua	Thu May 08 18:09:59 2014 +0100
+++ b/core/moduleapi.lua	Sat May 17 18:17:34 2014 +0100
@@ -13,6 +13,7 @@
 local logger = require "util.logger";
 local pluginloader = require "util.pluginloader";
 local timer = require "util.timer";
+local resolve_relative_path = require"util.paths".resolve_relative_path;
 
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local error, setmetatable, type = error, setmetatable, type;
@@ -380,7 +381,7 @@
 end
 
 function api:load_resource(path, mode)
-	path = config.resolve_relative_path(self:get_directory(), path);
+	path = resolve_relative_path(self:get_directory(), path);
 	return io.open(path, mode);
 end
 
--- a/plugins/mod_admin_telnet.lua	Thu May 08 18:09:59 2014 +0100
+++ b/plugins/mod_admin_telnet.lua	Sat May 17 18:17:34 2014 +0100
@@ -154,6 +154,14 @@
 	session.partial_data = data:match("[^\n]+$");
 end
 
+function console_listener.onreadtimeout(conn)
+	local session = sessions[conn];
+	if session then
+		session.send("\0");
+		return true;
+	end
+end
+
 function console_listener.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
@@ -212,9 +220,11 @@
 		print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
 		print [[c2s:show_insecure() - Show all unencrypted client connections]]
 		print [[c2s:show_secure() - Show all encrypted client connections]]
+		print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
 		print [[c2s:close(jid) - Close all sessions for the specified JID]]
 	elseif section == "s2s" then
 		print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
+		print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
 		print [[s2s:close(from, to) - Close a connection from one domain to another]]
 		print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
 	elseif section == "module" then
@@ -471,22 +481,28 @@
 	return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
 end
 
-def_env.hosts = {};
-function def_env.hosts:list()
-	for host, host_session in pairs(hosts) do
-		self.session.print(host);
+local function common_info(session, line)
+	if session.id then
+		line[#line+1] = "["..session.id.."]"
+	else
+		line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]"
 	end
-	return true, "Done";
-end
-
-function def_env.hosts:add(name)
 end
 
 local function session_flags(session, line)
 	line = line or {};
+	common_info(session, line);
+	if session.type == "c2s" then
+		local status, priority = "unavailable", tostring(session.priority or "-");
+		if session.presence then
+			status = session.presence:get_child_text("show") or "available";
+		end
+		line[#line+1] = status.."("..priority..")";
+	end
 	if session.cert_identity_status == "valid" then
-		line[#line+1] = "(secure)";
-	elseif session.secure then
+		line[#line+1] = "(authenticated)";
+	end
+	if session.secure then
 		line[#line+1] = "(encrypted)";
 	end
 	if session.compressed then
@@ -501,6 +517,23 @@
 	return table.concat(line, " ");
 end
 
+local function tls_info(session, line)
+	line = line or {};
+	common_info(session, line);
+	if session.secure then
+		local sock = session.conn and session.conn.socket and session.conn:socket();
+		if sock and sock.info then
+			local info = sock:info();
+			line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher);
+		else
+			line[#line+1] = "(cipher info unavailable)";
+		end
+	else
+		line[#line+1] = "(insecure)";
+	end
+	return table.concat(line, " ");
+end
+
 def_env.c2s = {};
 
 local function show_c2s(callback)
@@ -524,8 +557,9 @@
 	return true, "Total: "..count.." clients";
 end
 
-function def_env.c2s:show(match_jid)
+function def_env.c2s:show(match_jid, annotate)
 	local print, count = self.session.print, 0;
+	annotate = annotate or session_flags;
 	local curr_host;
 	show_c2s(function (jid, session)
 		if curr_host ~= session.host then
@@ -534,11 +568,7 @@
 		end
 		if (not match_jid) or jid:match(match_jid) then
 			count = count + 1;
-			local status, priority = "unavailable", tostring(session.priority or "-");
-			if session.presence then
-				status = session.presence:get_child_text("show") or "available";
-			end
-			print(session_flags(session, { "   "..jid.." - "..status.."("..priority..")" }));
+			print(annotate(session, { "  ", jid }));
 		end
 	end);
 	return true, "Total: "..count.." clients";
@@ -566,6 +596,10 @@
 	return true, "Total: "..count.." secure client connections";
 end
 
+function def_env.c2s:show_tls(match_jid)
+	return self:show(match_jid, tls_info);
+end
+
 function def_env.c2s:close(match_jid)
 	local count = 0;
 	show_c2s(function (jid, session)
@@ -579,8 +613,9 @@
 
 
 def_env.s2s = {};
-function def_env.s2s:show(match_jid)
+function def_env.s2s:show(match_jid, annotate)
 	local print = self.session.print;
+	annotate = annotate or session_flags;
 
 	local count_in, count_out = 0,0;
 	local s2s_list = { };
@@ -598,8 +633,7 @@
 			remotehost, localhost = session.from_host or "?", session.to_host or "?";
 		end
 		local sess_lines = { l = localhost, r = remotehost,
-			session_flags(session, { "", direction, remotehost or "?",
-				"["..session.type..tostring(session):match("[a-f0-9]*$").."]" })};
+			annotate(session, { "", direction, remotehost or "?" })};
 
 		if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
 			table.insert(s2s_list, sess_lines);
@@ -654,6 +688,10 @@
 	return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
 end
 
+function def_env.s2s:show_tls(match_jid)
+	return self:show(match_jid, tls_info);
+end
+
 local function print_subject(print, subject)
 	for _, entry in ipairs(subject) do
 		print(
@@ -823,9 +861,19 @@
 function def_env.host:list()
 	local print = self.session.print;
 	local i = 0;
+	local type;
 	for host in values(array.collect(keys(prosody.hosts)):sort()) do
 		i = i + 1;
-		print(host);
+		type = hosts[host].type;
+		if type == "local" then
+			print(host);
+		else
+			type = module:context(host):get_option_string("component_module", type);
+			if type ~= "component" then
+				type = type .. " component";
+			end
+			print(("%s (%s)"):format(host, type));
+		end
 	end
 	return true, i.." hosts";
 end
--- a/plugins/mod_s2s/mod_s2s.lua	Thu May 08 18:09:59 2014 +0100
+++ b/plugins/mod_s2s/mod_s2s.lua	Sat May 17 18:17:34 2014 +0100
@@ -150,6 +150,13 @@
 	module:hook("route/remote", route_to_new_session, -10);
 	module:hook("s2s-authenticated", make_authenticated, -1);
 	module:hook("s2s-read-timeout", keepalive, -1);
+	module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
+		if session.type == "s2sout" then
+			-- Stream is authenticated and we are seem to be done with feature negotiation,
+			-- so the stream is ready for stanzas.  RFC 6120 Section 4.3
+			mark_connected(session);
+		end
+	end, -1);
 end
 
 -- Stream is authorised, and ready for normal stanzas
@@ -219,7 +226,10 @@
 	end
 	session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host);
 
-	mark_connected(session);
+	if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then
+		-- Stream either used dialback for authentication or is an incoming stream.
+		mark_connected(session);
+	end
 
 	return true;
 end
--- a/plugins/mod_storage_sql.lua	Thu May 08 18:09:59 2014 +0100
+++ b/plugins/mod_storage_sql.lua	Sat May 17 18:17:34 2014 +0100
@@ -49,7 +49,7 @@
 end
 
 
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+local resolve_relative_path = require "util.paths".resolve_relative_path;
 
 local function test_connection()
 	if not connection then return nil; end
--- a/plugins/mod_storage_sql2.lua	Thu May 08 18:09:59 2014 +0100
+++ b/plugins/mod_storage_sql2.lua	Sat May 17 18:17:34 2014 +0100
@@ -2,7 +2,7 @@
 local json = require "util.json";
 local xml_parse = require "util.xml".parse;
 local uuid = require "util.uuid";
-local resolve_relative_path = require "core.configmanager".resolve_relative_path;
+local resolve_relative_path = require "util.paths".resolve_relative_path;
 
 local stanza_mt = require"util.stanza".stanza_mt;
 local getmetatable = getmetatable;
--- a/prosodyctl	Thu May 08 18:09:59 2014 +0100
+++ b/prosodyctl	Sat May 17 18:17:34 2014 +0100
@@ -797,8 +797,28 @@
 	local array, set = require "util.array", require "util.set";
 	local it = require "util.iterators";
 	local ok = true;
+	local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
+	local function enabled_hosts() return it.filter(disabled_hosts, pairs(config.getconfig())); end
+	if not what or what == "disabled" then
+		local disabled_hosts = set.new();
+		for host, host_options in it.filter("*", pairs(config.getconfig())) do
+			if host_options.enabled == false then
+				disabled_hosts:add(host);
+			end
+		end
+		if not disabled_hosts:empty() then
+			local msg = "Checks will be skipped for these disabled hosts: %s";
+			if what then msg = "These hosts are disabled: %s"; end
+			show_warning(msg, tostring(disabled_hosts));
+			if what then return 0; end
+			print""
+		end
+	end
 	if not what or what == "config" then
 		print("Checking config...");
+		local deprecated = set.new({
+			"bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login",
+		});
 		local known_global_options = set.new({
 			"pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize",
 			"umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings"
@@ -811,9 +831,27 @@
 			print("    No global options defined. Perhaps you have put a host definition at the top")
 			print("    of the config file? They should be at the bottom, see http://prosody.im/doc/configure#overview");
 		end
+		if it.count(enabled_hosts()) == 0 then
+			ok = false;
+			print("");
+			if it.count(it.filter("*", pairs(config))) == 0 then
+				print("    No hosts are defined, please add at least one VirtualHost section")
+			elseif config["*"]["enabled"] == false then
+				print("    No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section")
+			else
+				print("    All hosts are disabled. Remove enabled = false from at least one VirtualHost section")
+			end
+		end
 		-- Check for global options under hosts
 		local global_options = set.new(it.to_array(it.keys(config["*"])));
-		for host, options in it.filter("*", pairs(config)) do
+		local deprecated_global_options = set.intersection(global_options, deprecated);
+		if not deprecated_global_options:empty() then
+			print("");
+			print("    You have some deprecated options in the global section:");
+			print("    "..tostring(deprecated_global_options))
+			ok = false;
+		end
+		for host, options in enabled_hosts() do
 			local host_options = set.new(it.to_array(it.keys(options)));
 			local misplaced_options = set.intersection(host_options, known_global_options);
 			for name in pairs(options) do
@@ -898,7 +936,7 @@
 		
 		local v6_supported = not not socket.tcp6;
 		
-		for host, host_options in it.filter("*", pairs(config.getconfig())) do
+		for host, host_options in enabled_hosts() do
 			local all_targets_ok, some_targets_ok = true, false;
 			
 			local is_component = not not host_options.component_module;
@@ -1047,54 +1085,52 @@
 			print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking");
 			cert_ok = false
 		else
-			for host in pairs(hosts) do
-				if host ~= "*" then -- Should check global certs too.
-					print("Checking certificate for "..host);
-					-- First, let's find out what certificate this host uses.
-					local ssl_config = config.rawget(host, "ssl");
-					if not ssl_config then
-						local base_host = host:match("%.(.*)");
-						ssl_config = config.get(base_host, "ssl");
-					end
-					if not ssl_config then
-						print("  No 'ssl' option defined for "..host)
-						cert_ok = false
-					elseif not ssl_config.certificate then
-						print("  No 'certificate' set in ssl option for "..host)
-						cert_ok = false
-					elseif not ssl_config.key then
-						print("  No 'key' set in ssl option for "..host)
+			for host in enabled_hosts() do
+				print("Checking certificate for "..host);
+				-- First, let's find out what certificate this host uses.
+				local ssl_config = config.rawget(host, "ssl");
+				if not ssl_config then
+					local base_host = host:match("%.(.*)");
+					ssl_config = config.get(base_host, "ssl");
+				end
+				if not ssl_config then
+					print("  No 'ssl' option defined for "..host)
+					cert_ok = false
+				elseif not ssl_config.certificate then
+					print("  No 'certificate' set in ssl option for "..host)
+					cert_ok = false
+				elseif not ssl_config.key then
+					print("  No 'key' set in ssl option for "..host)
+					cert_ok = false
+				else
+					local key, err = io.open(ssl_config.key); -- Permissions check only
+					if not key then
+						print("    Could not open "..ssl_config.key..": "..err);
 						cert_ok = false
 					else
-						local key, err = io.open(ssl_config.key); -- Permissions check only
-						if not key then
-							print("    Could not open "..ssl_config.key..": "..err);
-							cert_ok = false
-						else
-							key:close();
-						end
-						local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
-						if not cert_fh then
-							print("    Could not open "..ssl_config.certificate..": "..err);
+						key:close();
+					end
+					local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
+					if not cert_fh then
+						print("    Could not open "..ssl_config.certificate..": "..err);
+						cert_ok = false
+					else
+						print("  Certificate: "..ssl_config.certificate)
+						local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
+						if not cert:validat(os.time()) then
+							print("    Certificate has expired.")
 							cert_ok = false
-						else
-							print("  Certificate: "..ssl_config.certificate)
-							local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
-							if not cert:validat(os.time()) then
-								print("    Certificate has expired.")
-								cert_ok = false
-							end
-							if config.get(host, "component_module") == nil
+						end
+						if config.get(host, "component_module") == nil
 							and not x509_verify_identity(host, "_xmpp-client", cert) then
-								print("    Not vaild for client connections to "..host..".")
-								cert_ok = false
-							end
-							if (not (config.get(name, "anonymous_login")
-								or config.get(name, "authentication") == "anonymous"))
+							print("    Not vaild for client connections to "..host..".")
+							cert_ok = false
+						end
+						if (not (config.get(host, "anonymous_login")
+							or config.get(host, "authentication") == "anonymous"))
 							and not x509_verify_identity(host, "_xmpp-client", cert) then
-								print("    Not vaild for server-to-server connections to "..host..".")
-								cert_ok = false
-							end
+							print("    Not vaild for server-to-server connections to "..host..".")
+							cert_ok = false
 						end
 					end
 				end
--- a/tools/jabberd14sql2prosody.lua	Thu May 08 18:09:59 2014 +0100
+++ b/tools/jabberd14sql2prosody.lua	Sat May 17 18:17:34 2014 +0100
@@ -428,7 +428,7 @@
 end
 
 -- import modules
-package.path = package.path.."..\?.lua;";
+package.path = package.path..";../?.lua;";
 
 local my_name = arg[0];
 if my_name:match("[/\\]") then
--- a/util-src/pposix.c	Thu May 08 18:09:59 2014 +0100
+++ b/util-src/pposix.c	Sat May 17 18:17:34 2014 +0100
@@ -674,6 +674,7 @@
 #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L || defined(_GNU_SOURCE)
 int lc_fallocate(lua_State* L)
 {
+	int ret;
 	off_t offset, len;
 	FILE *f = *(FILE**) luaL_checkudata(L, 1, LUA_FILEHANDLE);
 	if (f == NULL)
@@ -683,11 +684,15 @@
 	len = luaL_checkinteger(L, 3);
 
 #if defined(__linux__) && defined(_GNU_SOURCE)
-	if(fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len) == 0)
+	errno = 0;
+	ret = fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len);
+	if(ret == 0)
 	{
 		lua_pushboolean(L, 1);
 		return 1;
 	}
+	/* Some old versions of Linux apparently use the return value instead of errno */
+	if(errno == 0) errno = ret;
 
 	if(errno != ENOSYS && errno != EOPNOTSUPP)
 	{
@@ -701,7 +706,8 @@
 #warning Note that posix_fallocate() will still be used on filesystems that dont support fallocate()
 #endif
 
-	if(posix_fallocate(fileno(f), offset, len) == 0)
+	ret = posix_fallocate(fileno(f), offset, len);
+	if(ret == 0)
 	{
 		lua_pushboolean(L, 1);
 		return 1;
@@ -709,7 +715,7 @@
 	else
 	{
 		lua_pushnil(L);
-		lua_pushstring(L, strerror(errno));
+		lua_pushstring(L, strerror(ret));
 		/* posix_fallocate() can leave a bunch of NULs at the end, so we cut that
 		 * this assumes that offset == length of the file */
 		ftruncate(fileno(f), offset);
--- a/util/dataforms.lua	Thu May 08 18:09:59 2014 +0100
+++ b/util/dataforms.lua	Sat May 17 18:17:34 2014 +0100
@@ -94,6 +94,15 @@
 			end
 		end
 
+		local media = field.media;
+		if media then
+			form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
+			for _, val in ipairs(media) do
+				form:tag("uri", { type = val.type }):text(val.uri):up()
+			end
+			form:up();
+		end
+
 		if field.required then
 			form:tag("required"):up();
 		end
--- a/util/indexedbheap.lua	Thu May 08 18:09:59 2014 +0100
+++ b/util/indexedbheap.lua	Sat May 17 18:17:34 2014 +0100
@@ -113,23 +113,27 @@
 	k = _percolate_down(self.priorities, k, self.ids, self.index);
 end
 function indexed_heap:remove_index(k)
-	local size = #self.priorities;
+	local result = self.priorities[k];
+	if result == nil then return; end
 
-	local result = self.priorities[k];
 	local result_sync = self.ids[k];
 	local item = self.items[result_sync];
-	if result == nil then return; end
-	self.index[result_sync] = nil;
-	self.items[result_sync] = nil;
+	local size = #self.priorities;
 
 	self.priorities[k] = self.priorities[size];
 	self.ids[k] = self.ids[size];
 	self.index[self.ids[k]] = k;
+
 	t_remove(self.priorities);
 	t_remove(self.ids);
 
-	k = _percolate_up(self.priorities, k, self.ids, self.index);
-	k = _percolate_down(self.priorities, k, self.ids, self.index);
+	self.index[result_sync] = nil;
+	self.items[result_sync] = nil;
+
+	if size > k then
+		k = _percolate_up(self.priorities, k, self.ids, self.index);
+		k = _percolate_down(self.priorities, k, self.ids, self.index);
+	end
 
 	return result, item, result_sync;
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/paths.lua	Sat May 17 18:17:34 2014 +0100
@@ -0,0 +1,38 @@
+local path_sep = package.config:sub(1,1);
+
+local path_util = {}
+
+-- Helper function to resolve relative paths (needed by config)
+function path_util.resolve_relative_path(parent_path, path)
+	if path then
+		-- Some normalization
+		parent_path = parent_path:gsub("%"..path_sep.."+$", "");
+		path = path:gsub("^%.%"..path_sep.."+", "");
+
+		local is_relative;
+		if path_sep == "/" and path:sub(1,1) ~= "/" then
+			is_relative = true;
+		elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then
+			is_relative = true;
+		end
+		if is_relative then
+			return parent_path..path_sep..path;
+		end
+	end
+	return path;
+end
+
+-- Helper function to convert a glob to a Lua pattern
+function path_util.glob_to_pattern(glob)
+	return "^"..glob:gsub("[%p*?]", function (c)
+		if c == "*" then
+			return ".*";
+		elseif c == "?" then
+			return ".";
+		else
+			return "%"..c;
+		end
+	end).."$";
+end
+
+return path_util;
--- a/util/x509.lua	Thu May 08 18:09:59 2014 +0100
+++ b/util/x509.lua	Sat May 17 18:17:34 2014 +0100
@@ -20,11 +20,9 @@
 
 local nameprep = require "util.encodings".stringprep.nameprep;
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
+local base64 = require "util.encodings".base64;
 local log = require "util.logger".init("x509");
-local pairs, ipairs = pairs, ipairs;
 local s_format = string.format;
-local t_insert = table.insert;
-local t_concat = table.concat;
 
 module "x509"
 
@@ -214,4 +212,23 @@
 	return false
 end
 
+local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+
+function pem2der(pem)
+	local typ, data = pem:match(pat);
+	if typ and data then
+		return base64.decode(data), typ;
+	end
+end
+
+local wrap = ('.'):rep(64);
+local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
+
+function der2pem(data, typ)
+	typ = typ and typ:upper() or "CERTIFICATE";
+	data = base64.encode(data);
+	return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ);
+end
+
 return _M;