diff options
-rw-r--r-- | core/certmanager.lua | 2 | ||||
-rw-r--r-- | core/configmanager.lua | 45 | ||||
-rw-r--r-- | core/moduleapi.lua | 3 | ||||
-rw-r--r-- | plugins/mod_admin_telnet.lua | 88 | ||||
-rw-r--r-- | plugins/mod_s2s/mod_s2s.lua | 12 | ||||
-rw-r--r-- | plugins/mod_storage_sql.lua | 2 | ||||
-rw-r--r-- | plugins/mod_storage_sql2.lua | 2 | ||||
-rwxr-xr-x | prosodyctl | 122 | ||||
-rw-r--r-- | tools/jabberd14sql2prosody.lua | 2 | ||||
-rw-r--r-- | util-src/pposix.c | 12 | ||||
-rw-r--r-- | util/dataforms.lua | 9 | ||||
-rw-r--r-- | util/indexedbheap.lua | 18 | ||||
-rw-r--r-- | util/paths.lua | 38 | ||||
-rw-r--r-- | util/x509.lua | 23 |
14 files changed, 260 insertions, 118 deletions
diff --git a/core/certmanager.lua b/core/certmanager.lua index 74da771e..d6a59b9f 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -19,7 +19,7 @@ local t_concat = table.concat; 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; diff --git a/core/configmanager.lua b/core/configmanager.lua index d92120d0..1f7342b2 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -14,11 +14,15 @@ local format, math_max = string.format, math.max; 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 @@ function _M.set(host, key, value, _oldvalue) 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 @@ do 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.."]+$", ""); diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 5a24f69c..30d28418 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -13,6 +13,7 @@ local set = require "util.set"; 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 @@ function api:get_directory() 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 diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 71dfa300..9761d2f3 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -154,6 +154,14 @@ function console_listener.onincoming(conn, data) 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 @@ function commands.help(session, data) 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 @@ function def_env.config:reload() 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 @@ local function session_flags(session, line) 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 @@ function def_env.c2s:count(match_jid) 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 @@ function def_env.c2s:show(match_jid) 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 @@ function def_env.c2s:show_secure(match_jid) 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 @@ end 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 @@ function def_env.s2s:show(match_jid) 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 @@ function def_env.s2s:show(match_jid) 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 @@ end 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 diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 73d95970..263f24c0 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -150,6 +150,13 @@ function module.add_host(module) 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 @@ function make_authenticated(event) 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 diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 1f453d42..7b810ab8 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -49,7 +49,7 @@ local function db2uri(params) 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 diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index 7414e5ed..249c72a7 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -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; @@ -797,8 +797,28 @@ function commands.check(arg) 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 @@ function commands.check(arg) 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 @@ function commands.check(arg) 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 @@ function commands.check(arg) 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) + 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 - elseif not ssl_config.key then - print(" No 'key' set in ssl option for "..host) + 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); 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); + 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 - 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 - end - if config.get(host, "component_module") == nil + 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 diff --git a/tools/jabberd14sql2prosody.lua b/tools/jabberd14sql2prosody.lua index 03376b30..e43dc296 100644 --- a/tools/jabberd14sql2prosody.lua +++ b/tools/jabberd14sql2prosody.lua @@ -428,7 +428,7 @@ end end -- import modules -package.path = package.path.."..\?.lua;"; +package.path = package.path..";../?.lua;"; local my_name = arg[0]; if my_name:match("[/\\]") then diff --git a/util-src/pposix.c b/util-src/pposix.c index 73e0d6e3..9b3e97eb 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -674,6 +674,7 @@ int lc_meminfo(lua_State* L) #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 @@ int lc_fallocate(lua_State* L) 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 @@ int lc_fallocate(lua_State* L) #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 @@ int lc_fallocate(lua_State* L) 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); diff --git a/util/dataforms.lua b/util/dataforms.lua index b38d0e27..c352858c 100644 --- a/util/dataforms.lua +++ b/util/dataforms.lua @@ -94,6 +94,15 @@ function form_t.form(layout, data, formtype) 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 diff --git a/util/indexedbheap.lua b/util/indexedbheap.lua index 3cb03037..c60861e8 100644 --- a/util/indexedbheap.lua +++ b/util/indexedbheap.lua @@ -113,23 +113,27 @@ function indexed_heap:reprioritize(id, priority) 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_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 diff --git a/util/paths.lua b/util/paths.lua new file mode 100644 index 00000000..3e5744df --- /dev/null +++ b/util/paths.lua @@ -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; diff --git a/util/x509.lua b/util/x509.lua index 857f02a4..5e1b49e5 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -20,11 +20,9 @@ local nameprep = require "util.encodings".stringprep.nameprep; local idna_to_ascii = require "util.encodings".idna.to_ascii; +local base64 = require "util.encodings".base64; local log = require "util.logger".init("x509"); -local pairs, ipairs = pairs, ipairs; local s_format = string.format; -local t_insert = table.insert; -local t_concat = table.concat; module "x509" @@ -214,4 +212,23 @@ function verify_identity(host, service, cert) return false end +local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. +"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-"; + +function pem2der(pem) + local typ, data = pem:match(pat); + if typ and data then + return base64.decode(data), typ; + end +end + +local wrap = ('.'):rep(64); +local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n" + +function der2pem(data, typ) + typ = typ and typ:upper() or "CERTIFICATE"; + data = base64.encode(data); + return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ); +end + return _M; |