prosody-0.9.1/0000775000175000017500000000000012213321667013131 5ustar matthewmatthewprosody-0.9.1/.hg_archival.txt0000644000175000017500000000022412213321667016213 0ustar matthewmatthewrepo: 3e3171b59028ee70122cfec6ecf98f518f946b59 node: 6bc4077bc1f96ff83795fcc423ff270a28156d1c branch: default latesttag: 0.9.0 latesttagdistance: 9 prosody-0.9.1/plugins/0000775000175000017500000000000012213321667014612 5ustar matthewmatthewprosody-0.9.1/plugins/mod_tls.lua0000644000175000017500000000747312213321667016767 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; local create_context = require "core.certmanager".create_context; local st = require "util.stanza"; local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption"); local secure_s2s_only = module:get_option("s2s_require_encryption"); local allow_s2s_tls = module:get_option("s2s_allow_encryption") ~= false; local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls'; local starttls_attr = { xmlns = xmlns_starttls }; local starttls_proceed = st.stanza("proceed", starttls_attr); local starttls_failure = st.stanza("failure", starttls_attr); local c2s_feature = st.stanza("starttls", starttls_attr); local s2s_feature = st.stanza("starttls", starttls_attr); if secure_auth_only then c2s_feature:tag("required"):up(); end if secure_s2s_only then s2s_feature:tag("required"):up(); end local global_ssl_ctx = prosody.global_ssl_ctx; local hosts = prosody.hosts; local host = hosts[module.host]; local function can_do_tls(session) if session.type == "c2s_unauthed" then return session.conn.starttls and host.ssl_ctx_in; elseif session.type == "s2sin_unauthed" and allow_s2s_tls then return session.conn.starttls and host.ssl_ctx_in; elseif session.direction == "outgoing" and allow_s2s_tls then return session.conn.starttls and host.ssl_ctx; end return false; end -- Hook module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event) local origin = event.origin; if can_do_tls(origin) then (origin.sends2s or origin.send)(starttls_proceed); origin:reset_stream(); local host = origin.to_host or origin.host; local ssl_ctx = host and hosts[host].ssl_ctx_in or global_ssl_ctx; origin.conn:starttls(ssl_ctx); origin.log("debug", "TLS negotiation started for %s...", origin.type); origin.secure = false; else origin.log("warn", "Attempt to start TLS, but TLS is not available on this %s connection", origin.type); (origin.sends2s or origin.send)(starttls_failure); origin:close(); end return true; end); -- Advertize stream feature module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if can_do_tls(origin) then features:add_child(c2s_feature); end end); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; if can_do_tls(origin) then features:add_child(s2s_feature); end end); -- For s2sout connections, start TLS if we can module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) module:log("debug", "Received features element"); if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host); session.sends2s(""); return true; end end, 500); module:hook_stanza(xmlns_starttls, "proceed", function (session, stanza) module:log("debug", "Proceeding with TLS on s2sout..."); session:reset_stream(); local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx; session.conn:starttls(ssl_ctx); session.secure = false; return true; end); function module.load() local ssl_config = config.rawget(module.host, "ssl"); if not ssl_config then local base_host = module.host:match("%.(.*)"); ssl_config = config.get(base_host, "ssl"); end host.ssl_ctx = create_context(host.host, "client", ssl_config); -- for outgoing connections host.ssl_ctx_in = create_context(host.host, "server", ssl_config); -- for incoming connections end function module.unload() host.ssl_ctx = nil; host.ssl_ctx_in = nil; end prosody-0.9.1/plugins/mod_s2s/0000775000175000017500000000000012213321667016160 5ustar matthewmatthewprosody-0.9.1/plugins/mod_s2s/s2sout.lib.lua0000644000175000017500000002770012213321667020673 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- --- Module containing all the logic for connecting to a remote server local portmanager = require "core.portmanager"; local wrapclient = require "net.server".wrapclient; local initialize_filters = require "util.filters".initialize; local idna_to_ascii = require "util.encodings".idna.to_ascii; local new_ip = require "util.ip".new_ip; local rfc6724_dest = require "util.rfc6724".destination; local socket = require "socket"; local adns = require "net.adns"; local dns = require "net.dns"; local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs; local local_addresses = require "util.net".local_addresses; local s2s_destroy_session = require "core.s2smanager".destroy_session; local log = module._log; local sources = {}; local has_ipv4, has_ipv6; local dns_timeout = module:get_option_number("dns_timeout", 15); dns.settimeout(dns_timeout); local max_dns_depth = module:get_option_number("dns_max_depth", 3); local s2sout = {}; local s2s_listener; function s2sout.set_listener(listener) s2s_listener = listener; end local function compare_srv_priorities(a,b) return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight); end function s2sout.initiate_connection(host_session) initialize_filters(host_session); host_session.version = 1; -- Kick the connection attempting machine into life if not s2sout.attempt_connection(host_session) then -- Intentionally not returning here, the -- session is needed, connected or not s2s_destroy_session(host_session); end if not host_session.sends2s then -- A sends2s which buffers data (until the stream is opened) -- note that data in this buffer will be sent before the stream is authed -- and will not be ack'd in any way, successful or otherwise local buffer; function host_session.sends2s(data) if not buffer then buffer = {}; host_session.send_buffer = buffer; end log("debug", "Buffering data on unconnected s2sout to %s", tostring(host_session.to_host)); buffer[#buffer+1] = data; log("debug", "Buffered item %d: %s", #buffer, tostring(data)); end end end function s2sout.attempt_connection(host_session, err) local to_host = host_session.to_host; local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269; if not connect_host then return false; end if not err then -- This is our first attempt log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host); host_session.connecting = true; local handle; handle = adns.lookup(function (answer) handle = nil; host_session.connecting = nil; if answer and #answer > 0 then log("debug", "%s has SRV records, handling...", to_host); local srv_hosts = { answer = answer }; host_session.srv_hosts = srv_hosts; for _, record in ipairs(answer) do t_insert(srv_hosts, record.srv); end if #srv_hosts == 1 and srv_hosts[1].target == "." then log("debug", "%s does not provide a XMPP service", to_host); s2s_destroy_session(host_session, err); -- Nothing to see here return; end t_sort(srv_hosts, compare_srv_priorities); local srv_choice = srv_hosts[1]; host_session.srv_choice = 1; if srv_choice then connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port; log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port); end else log("debug", "%s has no SRV records, falling back to A/AAAA", to_host); end -- Try with SRV, or just the plain hostname if no SRV local ok, err = s2sout.try_connect(host_session, connect_host, connect_port); if not ok then if not s2sout.attempt_connection(host_session, err) then -- No more attempts will be made s2s_destroy_session(host_session, err); end end end, "_xmpp-server._tcp."..connect_host..".", "SRV"); return true; -- Attempt in progress elseif host_session.ip_hosts then return s2sout.try_connect(host_session, connect_host, connect_port, err); elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV host_session.srv_choice = host_session.srv_choice + 1; local srv_choice = host_session.srv_hosts[host_session.srv_choice]; connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port; host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port); else host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host)); -- We're out of options return false; end if not (connect_host and connect_port) then -- Likely we couldn't resolve DNS log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host)); return false; end return s2sout.try_connect(host_session, connect_host, connect_port); end function s2sout.try_next_ip(host_session) host_session.connecting = nil; host_session.ip_choice = host_session.ip_choice + 1; local ip = host_session.ip_hosts[host_session.ip_choice]; local ok, err= s2sout.make_connect(host_session, ip.ip, ip.port); if not ok then if not s2sout.attempt_connection(host_session, err or "closed") then err = err and (": "..err) or ""; s2s_destroy_session(host_session, "Connection failed"..err); end end end function s2sout.try_connect(host_session, connect_host, connect_port, err) host_session.connecting = true; if not err then local IPs = {}; host_session.ip_hosts = IPs; local handle4, handle6; local have_other_result = not(has_ipv4) or not(has_ipv6) or false; if has_ipv4 then handle4 = adns.lookup(function (reply, err) handle4 = nil; -- COMPAT: This is a compromise for all you CNAME-(ab)users :) if not (reply and reply[#reply] and reply[#reply].a) then local count = max_dns_depth; reply = dns.peek(connect_host, "CNAME", "IN"); while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count); reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN"); count = count - 1; end end -- end of CNAME resolving if reply and reply[#reply] and reply[#reply].a then for _, ip in ipairs(reply) do log("debug", "DNS reply for %s gives us %s", connect_host, ip.a); IPs[#IPs+1] = new_ip(ip.a, "IPv4"); end end if have_other_result then if #IPs > 0 then rfc6724_dest(host_session.ip_hosts, sources); for i = 1, #IPs do IPs[i] = {ip = IPs[i], port = connect_port}; end host_session.ip_choice = 0; s2sout.try_next_ip(host_session); else log("debug", "DNS lookup failed to get a response for %s", connect_host); host_session.ip_hosts = nil; if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can log("debug", "No other records to try for %s - destroying", host_session.to_host); err = err and (": "..err) or ""; s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't end end else have_other_result = true; end end, connect_host, "A", "IN"); else have_other_result = true; end if has_ipv6 then handle6 = adns.lookup(function (reply, err) handle6 = nil; if reply and reply[#reply] and reply[#reply].aaaa then for _, ip in ipairs(reply) do log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa); IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6"); end end if have_other_result then if #IPs > 0 then rfc6724_dest(host_session.ip_hosts, sources); for i = 1, #IPs do IPs[i] = {ip = IPs[i], port = connect_port}; end host_session.ip_choice = 0; s2sout.try_next_ip(host_session); else log("debug", "DNS lookup failed to get a response for %s", connect_host); host_session.ip_hosts = nil; if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can log("debug", "No other records to try for %s - destroying", host_session.to_host); err = err and (": "..err) or ""; s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't end end else have_other_result = true; end end, connect_host, "AAAA", "IN"); else have_other_result = true; end return true; elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try s2sout.try_next_ip(host_session); else host_session.ip_hosts = nil; if not s2sout.attempt_connection(host_session, "out of IP addresses") then -- Retry if we can log("debug", "No other records to try for %s - destroying", host_session.to_host); err = err and (": "..err) or ""; s2s_destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't return false; end end return true; end function s2sout.make_connect(host_session, connect_host, connect_port) (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port); -- Reset secure flag in case this is another -- connection attempt after a failed STARTTLS host_session.secure = nil; local conn, handler; local proto = connect_host.proto; if proto == "IPv4" then conn, handler = socket.tcp(); elseif proto == "IPv6" and socket.tcp6 then conn, handler = socket.tcp6(); else handler = "Unsupported protocol: "..tostring(proto); end if not conn then log("warn", "Failed to create outgoing connection, system error: %s", handler); return false, handler; end conn:settimeout(0); local success, err = conn:connect(connect_host.addr, connect_port); if not success and err ~= "timeout" then log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err); return false, err; end conn = wrapclient(conn, connect_host.addr, connect_port, s2s_listener, "*a"); host_session.conn = conn; local filter = initialize_filters(host_session); local w, log = conn.write, host_session.log; host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); if t.name then t = filter("stanzas/out", t); end if t then t = filter("bytes/out", tostring(t)); if t then return w(conn, tostring(t)); end end end -- Register this outgoing connection so that xmppserver_listener knows about it -- otherwise it will assume it is a new incoming connection s2s_listener.register_outgoing(conn, host_session); log("debug", "Connection attempt in progress..."); return true; end module:hook_global("service-added", function (event) if event.name ~= "s2s" then return end local s2s_sources = portmanager.get_active_services():get("s2s"); if not s2s_sources then module:log("warn", "s2s not listening on any ports, outgoing connections may fail"); return; end for source, _ in pairs(s2s_sources) do if source == "*" or source == "0.0.0.0" then for _, addr in ipairs(local_addresses("ipv4", true)) do sources[#sources + 1] = new_ip(addr, "IPv4"); end elseif source == "::" then for _, addr in ipairs(local_addresses("ipv6", true)) do sources[#sources + 1] = new_ip(addr, "IPv6"); end else sources[#sources + 1] = new_ip(source, (source:find(":") and "IPv6") or "IPv4"); end end for i = 1,#sources do if sources[i].proto == "IPv6" then has_ipv6 = true; elseif sources[i].proto == "IPv4" then has_ipv4 = true; end end end); return s2sout; prosody-0.9.1/plugins/mod_s2s/mod_s2s.lua0000644000175000017500000005664012213321667020242 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local prosody = prosody; local hosts = prosody.hosts; local core_process_stanza = prosody.core_process_stanza; local tostring, type = tostring, type; local t_insert = table.insert; local xpcall, traceback = xpcall, debug.traceback; local NULL = {}; local add_task = require "util.timer".add_task; local st = require "util.stanza"; local initialize_filters = require "util.filters".initialize; local nameprep = require "util.encodings".stringprep.nameprep; local new_xmpp_stream = require "util.xmppstream".new; local s2s_new_incoming = require "core.s2smanager".new_incoming; local s2s_new_outgoing = require "core.s2smanager".new_outgoing; local s2s_destroy_session = require "core.s2smanager".destroy_session; local uuid_gen = require "util.uuid".generate; local cert_verify_identity = require "util.x509".verify_identity; local fire_global_event = prosody.events.fire_event; 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; local require_encryption = module:get_option_boolean("s2s_require_encryption", false); local sessions = module:shared("sessions"); local log = module._log; --- Handle stanzas to remote domains local bouncy_stanzas = { message = true, presence = true, iq = true }; local function bounce_sendq(session, reason) local sendq = session.sendq; if not sendq then return; end session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host)); local dummy = { type = "s2sin"; send = function(s) (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback()); end; dummy = true; }; for i, data in ipairs(sendq) do local reply = data[2]; if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then reply.attr.type = "error"; reply:tag("error", {type = "cancel"}) :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); if reason then reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) :text("Server-to-server connection failed: "..reason):up(); end core_process_stanza(dummy, reply); end sendq[i] = nil; end session.sendq = nil; end -- Handles stanzas to existing s2s sessions function route_to_existing_session(event) local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; if not hosts[from_host] then log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host); return false; end if hosts[to_host] then log("warn", "Attempt to route stanza to a remote %s - a host we do serve?!", from_host); return false; end local host = hosts[from_host].s2sout[to_host]; if host then -- We have a connection to this host already if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); -- Queue stanza until we are able to send it if host.sendq then t_insert(host.sendq, {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)}); else host.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; end host.log("debug", "stanza [%s] queued ", stanza.name); return true; elseif host.type == "local" or host.type == "component" then log("error", "Trying to send a stanza to ourselves??") log("error", "Traceback: %s", traceback()); log("error", "Stanza: %s", tostring(stanza)); return false; else (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host); -- FIXME if host.from_host ~= from_host then log("error", "WARNING! This might, possibly, be a bug, but it might not..."); log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host)); end if host.sends2s(stanza) then host.log("debug", "stanza sent over %s", host.type); return true; end end end end -- Create a new outgoing session for a stanza function route_to_new_session(event) local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; log("debug", "opening a new outgoing connection for this stanza"); local host_session = s2s_new_outgoing(from_host, to_host); -- Store in buffer host_session.bounce_sendq = bounce_sendq; host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); s2sout.initiate_connection(host_session); if (not host_session.connecting) and (not host_session.conn) then log("warn", "Connection to %s failed already, destroying session...", to_host); s2s_destroy_session(host_session, "Connection failed"); return false; end return true; end function module.add_host(module) if module:get_option_boolean("disallow_s2s", false) then 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, -1); module:hook("route/remote", route_to_new_session, -10); module:hook("s2s-authenticated", make_authenticated, -1); end -- Stream is authorised, and ready for normal stanzas function mark_connected(session) local sendq, send = session.sendq, session.sends2s; local from, to = session.from_host, session.to_host; session.log("info", "%s s2s connection %s->%s complete", session.direction, from, to); local event_data = { session = session }; if session.type == "s2sout" then fire_global_event("s2sout-established", event_data); hosts[from].events.fire_event("s2sout-established", event_data); else local host_session = hosts[to]; session.send = function(stanza) return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); end; fire_global_event("s2sin-established", event_data); hosts[to].events.fire_event("s2sin-established", event_data); end if session.direction == "outgoing" then if sendq then session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host); for i, data in ipairs(sendq) do send(data[1]); sendq[i] = nil; end session.sendq = nil; end session.ip_hosts = nil; session.srv_hosts = nil; end end function make_authenticated(event) local session, host = event.session, event.host; if not session.secure then if require_encryption or (secure_auth and not(insecure_domains[host])) or secure_domains[host] then session:close({ condition = "policy-violation", text = "Encrypted server-to-server communication is required but was not " ..((session.direction == "outgoing" and "offered") or "used") }); end end if hosts[host] then session:close({ condition = "undefined-condition", text = "Attempt to authenticate as a host we serve" }); end if session.type == "s2sout_unauthed" then session.type = "s2sout"; elseif session.type == "s2sin_unauthed" then session.type = "s2sin"; if host then if not session.hosts[host] then session.hosts[host] = {}; end session.hosts[host].authed = true; end elseif session.type == "s2sin" and host then if not session.hosts[host] then session.hosts[host] = {}; end session.hosts[host].authed = true; else return false; end session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); mark_connected(session); return true; end --- Helper to check that a session peer's certificate is valid local function check_cert_status(session) local host = session.direction == "outgoing" and session.to_host or session.from_host local conn = session.conn:socket() local cert if conn.getpeercertificate then cert = conn:getpeercertificate() end if cert then local chain_valid, errors; if conn.getpeerverification then chain_valid, errors = conn:getpeerverification(); elseif conn.getpeerchainvalid then -- COMPAT mw/luasec-hg chain_valid, errors = conn:getpeerchainvalid(); errors = (not chain_valid) and { { errors } } or nil; else chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } }; end -- Is there any interest in printing out all/the number of errors here? if not chain_valid then (session.log or log)("debug", "certificate chain validation result: invalid"); for depth, t in pairs(errors or NULL) do (session.log or log)("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", ")) end session.cert_chain_status = "invalid"; else (session.log or log)("debug", "certificate chain validation result: valid"); session.cert_chain_status = "valid"; -- We'll go ahead and verify the asserted identity if the -- connecting server specified one. if host then if cert_verify_identity(host, "xmpp-server", cert) then session.cert_identity_status = "valid" else session.cert_identity_status = "invalid" end (session.log or log)("debug", "certificate identity validation result: %s", session.cert_identity_status); end end end return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert }); end --- XMPP stream event handlers local stream_callbacks = { default_ns = "jabber:server", handlestanza = core_process_stanza }; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; function stream_callbacks.streamopened(session, attr) local send = session.sends2s; session.version = tonumber(attr.version) or 0; -- TODO: Rename session.secure to session.encrypted if session.secure == false then session.secure = true; -- Check if TLS compression is used local sock = session.conn:socket(); if sock.info then session.compressed = sock:info"compression"; elseif sock.compression then session.compressed = sock:compression(); --COMPAT mw/luasec-hg end end if session.direction == "incoming" then -- Send a reply stream header -- Validate to/from local to, from = nameprep(attr.to), nameprep(attr.from); if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); return; end if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts) session:close({ condition = "improper-addressing", text = "Invalid 'from' address" }); return; end -- Set session.[from/to]_host if they have not been set already and if -- this session isn't already authenticated if session.type == "s2sin_unauthed" and from and not session.from_host then session.from_host = from; elseif from ~= session.from_host then session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" }); return; end if session.type == "s2sin_unauthed" and to and not session.to_host then session.to_host = to; elseif to ~= session.to_host then session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" }); return; end -- For convenience we'll put the sanitised values into these variables to, from = session.to_host, session.from_host; session.streamid = uuid_gen(); (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag()); if to then if not hosts[to] then -- Attempting to connect to a host we don't serve session:close({ condition = "host-unknown"; text = "This host does not serve "..to }); return; elseif not hosts[to].modules.s2s then -- Attempting to connect to a host that disallows s2s session:close({ condition = "policy-violation"; text = "Server-to-server communication is disabled for this host"; }); return; end end if hosts[from] then session:close({ condition = "undefined-condition", text = "Attempt to connect from a host we serve" }); return; end if session.secure and not session.cert_chain_status then if check_cert_status(session) == false then return; end end session:open_stream(session.to_host, session.from_host) if session.version >= 1.0 then local features = st.stanza("stream:features"); if to then hosts[to].events.fire_event("s2s-stream-features", { origin = session, features = features }); else (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or "unknown host"); end log("debug", "Sending stream features: %s", tostring(features)); send(features); end elseif session.direction == "outgoing" then -- If we are just using the connection for verifying dialback keys, we won't try and auth it if not attr.id then error("stream response did not give us a streamid!!!"); end session.streamid = attr.id; if session.secure and not session.cert_chain_status then if check_cert_status(session) == false then return; end end -- Send unauthed buffer -- (stanzas which are fine to send before dialback) -- Note that this is *not* the stanza queue (which -- we can only send if auth succeeds) :) local send_buffer = session.send_buffer; if send_buffer and #send_buffer > 0 then log("debug", "Sending s2s send_buffer now..."); for i, data in ipairs(send_buffer) do session.sends2s(tostring(data)); send_buffer[i] = nil; end end session.send_buffer = nil; -- If server is pre-1.0, don't wait for features, just do dialback if session.version < 1.0 then if not session.dialback_verifying then hosts[session.from_host].events.fire_event("s2sout-authenticate-legacy", { origin = session }); else mark_connected(session); end end end session.notopen = nil; end function stream_callbacks.streamclosed(session) (session.log or log)("debug", "Received "); session:close(false); end function stream_callbacks.error(session, error, data) if error == "no-stream" then session:close("invalid-namespace"); elseif error == "parse-error" then session.log("debug", "Server-to-server XML parse error: %s", tostring(error)); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:children() do if child.attr.xmlns == xmlns_xmpp_streams then if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end 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; end stanza = session.filter("stanzas/in", stanza); if stanza then return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); end end local listener = {}; --- Session methods local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local function session_close(session, reason, remote_reason) local log = session.log or log; if session.conn then if session.notopen then 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 log("debug", "Disconnecting %s[%s], is: %s", session.host or "(unknown host)", session.type, reason); session.sends2s(st.stanza("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(); if reason.text then stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stanza:add_child(reason.extra); end log("debug", "Disconnecting %s[%s], is: %s", session.host or "(unknown host)", session.type, tostring(stanza)); session.sends2s(stanza); elseif reason.name then -- a stanza log("debug", "Disconnecting %s->%s[%s], is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason)); session.sends2s(reason); end end end session.sends2s(""); function session.sends2s() return false; end local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason; session.log("info", "%s s2s stream %s->%s closed: %s", session.direction, session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed"); -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; if reason == nil and not session.notopen and session.type == "s2sin" then add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); s2s_destroy_session(session, reason); conn:close(); end end); else s2s_destroy_session(session, reason); conn:close(); -- Close immediately, as this is an outgoing connection or is not authed end end end function session_open_stream(session, from, to) local attr = { ["xmlns:stream"] = 'http://etherx.jabber.org/streams', xmlns = 'jabber:server', version = session.version and (session.version > 0 and "1.0" or nil), ["xml:lang"] = 'en', id = session.streamid, from = from, to = to, } if not from or (hosts[from] and hosts[from].modules.dialback) then attr["xmlns:db"] = 'jabber:server:dialback'; end session.sends2s(""); session.sends2s(st.stanza("stream:stream", attr):top_tag()); return true; end -- Session initialization logic shared by incoming and outgoing local function initialize_session(session) local stream = new_xmpp_stream(session, stream_callbacks); session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.stream:reset(); end session.open_stream = session_open_stream; local filter = session.filter; function session.data(data) data = filter("bytes/in", data); if data then local ok, err = stream:feed(data); if ok then return; end (session.log or log)("warn", "Received invalid XML: %s", data); (session.log or log)("warn", "Problem was: %s", err); session:close("not-well-formed"); end end session.close = session_close; local handlestanza = stream_callbacks.handlestanza; function session.dispatch_stanza(session, stanza) return handlestanza(session, stanza); end add_task(connect_timeout, function () if session.type == "s2sin" or session.type == "s2sout" then return; -- Ok, we're connected elseif session.type == "s2s_destroyed" then return; -- Session already destroyed end -- Not connected, need to close session and clean up (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity", session.from_host or "(unknown)", session.to_host or "(unknown)"); session:close("connection-timeout"); end); 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); sessions[conn] = session; session.log("debug", "Incoming s2s connection"); local filter = initialize_filters(session); local w = conn.write; session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); if t.name then t = filter("stanzas/out", t); end if t then t = filter("bytes/out", tostring(t)); if t then return w(conn, t); end end end initialize_session(session); else -- Outgoing session connected session:open_stream(session.from_host, session.to_host); end end function listener.onincoming(conn, data) local session = sessions[conn]; if session then session.data(data); end end function listener.onstatus(conn, status) if status == "ssl-handshake-complete" then local session = sessions[conn]; if session and session.direction == "outgoing" then session.log("debug", "Sending stream header..."); session:open_stream(session.from_host, session.to_host); end end end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then sessions[conn] = nil; if err and session.direction == "outgoing" and session.notopen then (session.log or log)("debug", "s2s connection attempt failed: %s", err); if s2sout.attempt_connection(session, err) then (session.log or log)("debug", "...so we're going to try another target"); return; -- Session lives for now end end (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "connection closed")); s2s_destroy_session(session, err); end end function listener.register_outgoing(conn, session) session.direction = "outgoing"; sessions[conn] = session; initialize_session(session); end function check_auth_policy(event) local host, session = event.host, event.session; local must_secure = secure_auth; if not must_secure and secure_domains[host] then must_secure = true; elseif must_secure and insecure_domains[host] then must_secure = false; end if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") 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 }); else -- Close outgoing connections without warning session:close(false); end return false; end end module:hook("s2s-check-certificate", check_auth_policy, -1); s2sout.set_listener(listener); module:hook("server-stopping", function(event) local reason = event.reason; for _, session in pairs(sessions) do session:close{ condition = "system-shutdown", text = reason }; end end,500); module:provides("net", { name = "s2s"; listener = listener; default_port = 5269; encryption = "starttls"; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; }; }); prosody-0.9.1/plugins/mod_groups.lua0000644000175000017500000000716712213321667017504 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local groups; local members; local groups_file; local jid, datamanager = require "util.jid", require "util.datamanager"; local jid_prep = jid.prep; local module_host = module:get_host(); function inject_roster_contacts(username, host, roster) --module:log("debug", "Injecting group members to roster"); local bare_jid = username.."@"..host; if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups local function import_jids_to_roster(group_name) for jid in pairs(groups[group_name]) do -- Add them to roster --module:log("debug", "processing jid %s in group %s", tostring(jid), tostring(group_name)); if jid ~= bare_jid then if not roster[jid] then roster[jid] = {}; end roster[jid].subscription = "both"; if groups[group_name][jid] then roster[jid].name = groups[group_name][jid]; end if not roster[jid].groups then roster[jid].groups = { [group_name] = true }; end roster[jid].groups[group_name] = true; roster[jid].persist = false; end end end -- Find groups this JID is a member of if members[bare_jid] then for _, group_name in ipairs(members[bare_jid]) do --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end -- Import public groups if members[false] then for _, group_name in ipairs(members[false]) do --module:log("debug", "Importing group %s", group_name); import_jids_to_roster(group_name); end end if roster[false] then roster[false].version = true; end end function remove_virtual_contacts(username, host, datastore, data) if host == module_host and datastore == "roster" then local new_roster = {}; for jid, contact in pairs(data) do if contact.persist ~= false then new_roster[jid] = contact; end end if new_roster[false] then new_roster[false].version = nil; -- Version is void end return username, host, datastore, new_roster; end return username, host, datastore, data; end function module.load() groups_file = module:get_option_string("groups_file"); if not groups_file then return; end module:hook("roster-load", inject_roster_contacts); datamanager.add_callback(remove_virtual_contacts); groups = { default = {} }; members = { }; local curr_group = "default"; for line in io.lines(groups_file) do if line:match("^%s*%[.-%]%s*$") then curr_group = line:match("^%s*%[(.-)%]%s*$"); if curr_group:match("^%+") then curr_group = curr_group:gsub("^%+", ""); if not members[false] then members[false] = {}; end members[false][#members[false]+1] = curr_group; -- Is a public group end module:log("debug", "New group: %s", tostring(curr_group)); groups[curr_group] = groups[curr_group] or {}; else -- Add JID local entryjid, name = line:match("([^=]*)=?(.*)"); module:log("debug", "entryjid = '%s', name = '%s'", entryjid, name); local jid; jid = jid_prep(entryjid:match("%S+")); if jid then module:log("debug", "New member of %s: %s", tostring(curr_group), tostring(jid)); groups[curr_group][jid] = name or false; members[jid] = members[jid] or {}; members[jid][#members[jid]+1] = curr_group; end end end module:log("info", "Groups loaded successfully"); end function module.unload() datamanager.remove_callback(remove_virtual_contacts); end -- Public for other modules to access function group_contains(group_name, jid) return groups[group_name][jid]; end prosody-0.9.1/plugins/mod_net_multiplex.lua0000644000175000017500000000357112213321667021051 0ustar matthewmatthewmodule:set_global(); local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024); local portmanager = require "core.portmanager"; local available_services = {}; local function add_service(service) local multiplex_pattern = service.multiplex and service.multiplex.pattern; if multiplex_pattern then module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern); available_services[service] = multiplex_pattern; else module:log("debug", "Service %q is not multiplex-capable", service.name); end end module:hook("service-added", function (event) add_service(event.service); end); module:hook("service-removed", function (event) available_services[event.service] = nil; end); for service_name, services in pairs(portmanager.get_registered_services()) do for i, service in ipairs(services) do add_service(service); end end local buffers = {}; local listener = { default_mode = "*a" }; function listener.onconnect() end function listener.onincoming(conn, data) if not data then return; end local buf = buffers[conn]; buffers[conn] = nil; buf = buf and buf..data or data; for service, multiplex_pattern in pairs(available_services) do if buf:match(multiplex_pattern) then module:log("debug", "Routing incoming connection to %s", service.name); local listener = service.listener; conn:setlistener(listener); local onconnect = listener.onconnect; if onconnect then onconnect(conn) end return listener.onincoming(conn, buf); end end if #buf > max_buffer_len then -- Give up conn:close(); else buffers[conn] = buf; end end function listener.ondisconnect(conn, err) buffers[conn] = nil; -- warn if no buffer? end module:provides("net", { name = "multiplex"; config_prefix = ""; listener = listener; }); module:provides("net", { name = "multiplex_ssl"; config_prefix = "ssl"; listener = listener; }); prosody-0.9.1/plugins/mod_auth_internal_plain.lua0000644000175000017500000000412012213321667022167 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local 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 for user %s at host %s", username, host); local credentials = accounts:get(username) or {}; if password == credentials.password then return true; else return nil, "Auth failed. Invalid username or password."; end end function provider.get_password(username) log("debug", "get_password for username '%s' at host '%s'", username, host); return (accounts:get(username) or {}).password; end function provider.set_password(username, password) local account = accounts:get(username); if account then account.password = password; return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) 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"; end return true; end function provider.users() return accounts:users(); end function provider.create_user(username, password) return accounts:set(username, {password = password}); end function provider.delete_user(username) return accounts:set(username, nil); end function provider.get_sasl_handler() local getpass_authentication_profile = { plain = function(sasl, username, realm) local password = usermanager.get_password(username, realm); if not password then return "", nil; end return password, true; end }; return new_sasl(host, getpass_authentication_profile); end module:provides("auth", provider); prosody-0.9.1/plugins/mod_iq.lua0000644000175000017500000000452412213321667016570 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local full_sessions = prosody.full_sessions; if module:get_host_type() == "local" then module:hook("iq/full", function(data) -- IQ to full JID recieved local origin, stanza = data.origin, data.stanza; local session = full_sessions[stanza.attr.to]; if not (session and session.send(stanza)) then if stanza.attr.type == "get" or stanza.attr.type == "set" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end end return true; end); end module:hook("iq/bare", function(data) -- IQ to bare JID recieved local stanza = data.stanza; local type = stanza.attr.type; -- TODO fire post processing events if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/bare/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/bare/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/bare/"..stanza.attr.id, data); end end); module:hook("iq/self", function(data) -- IQ to self JID recieved local stanza = data.stanza; local type = stanza.attr.type; if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/self/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/self/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/self/"..stanza.attr.id, data); end end); module:hook("iq/host", function(data) -- IQ to a local host recieved local stanza = data.stanza; local type = stanza.attr.type; if type == "get" or type == "set" then local child = stanza.tags[1]; local xmlns = child.attr.xmlns or "jabber:client"; local ret = module:fire_event("iq/host/"..xmlns..":"..child.name, data); if ret ~= nil then return ret; end return module:fire_event("iq-"..type.."/host/"..xmlns..":"..child.name, data); else return module:fire_event("iq-"..type.."/host/"..stanza.attr.id, data); end end); prosody-0.9.1/plugins/mod_time.lua0000644000175000017500000000247012213321667017113 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local datetime = require "util.datetime".datetime; local legacy = require "util.datetime".legacy; -- XEP-0202: Entity Time module:add_feature("urn:xmpp:time"); local function time_handler(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then origin.send(st.reply(stanza):tag("time", {xmlns="urn:xmpp:time"}) :tag("tzo"):text("+00:00"):up() -- TODO get the timezone in a platform independent fashion :tag("utc"):text(datetime())); return true; end end module:hook("iq/bare/urn:xmpp:time:time", time_handler); module:hook("iq/host/urn:xmpp:time:time", time_handler); -- XEP-0090: Entity Time (deprecated) module:add_feature("jabber:iq:time"); local function legacy_time_handler(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then origin.send(st.reply(stanza):tag("query", {xmlns="jabber:iq:time"}) :tag("utc"):text(legacy())); return true; end end module:hook("iq/bare/jabber:iq:time:query", legacy_time_handler); module:hook("iq/host/jabber:iq:time:query", legacy_time_handler); prosody-0.9.1/plugins/mod_disco.lua0000644000175000017500000001444312213321667017261 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local get_children = require "core.hostmanager".get_children; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza" local calculate_hash = require "util.caps".calculate_hash; local disco_items = module:get_option("disco_items") or {}; do -- validate disco_items for _, item in ipairs(disco_items) do local err; if type(item) ~= "table" then err = "item is not a table"; elseif type(item[1]) ~= "string" then err = "item jid is not a string"; elseif item[2] and type(item[2]) ~= "string" then err = "item name is not a string"; end if err then module:log("error", "option disco_items is malformed: %s", err); disco_items = {}; -- TODO clean up data instead of removing it? break; end end end module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router module:add_feature("http://jabber.org/protocol/disco#info"); module:add_feature("http://jabber.org/protocol/disco#items"); -- Generate and cache disco result and caps hash local _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash; local function build_server_disco_info() local query = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" }); local done = {}; for _,identity in ipairs(module:get_host_items("identity")) do local identity_s = identity.category.."\0"..identity.type; if not done[identity_s] then query:tag("identity", identity):up(); done[identity_s] = true; end end for _,feature in ipairs(module:get_host_items("feature")) do if not done[feature] then query:tag("feature", {var=feature}):up(); done[feature] = true; end end for _,extension in ipairs(module:get_host_items("extension")) do if not done[extension] then query:add_child(extension); done[extension] = true; end end _cached_server_disco_info = query; _cached_server_caps_hash = calculate_hash(query); _cached_server_caps_feature = st.stanza("c", { xmlns = "http://jabber.org/protocol/caps"; hash = "sha-1"; node = "http://prosody.im"; ver = _cached_server_caps_hash; }); end local function clear_disco_cache() _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash = nil, nil, nil; end local function get_server_disco_info() if not _cached_server_disco_info then build_server_disco_info(); end return _cached_server_disco_info; end local function get_server_caps_feature() if not _cached_server_caps_feature then build_server_disco_info(); end return _cached_server_caps_feature; end local function get_server_caps_hash() if not _cached_server_caps_hash then build_server_disco_info(); end return _cached_server_caps_hash; end module:hook("item-added/identity", clear_disco_cache); module:hook("item-added/feature", clear_disco_cache); module:hook("item-added/extension", clear_disco_cache); module:hook("item-removed/identity", clear_disco_cache); module:hook("item-removed/feature", clear_disco_cache); module:hook("item-removed/extension", clear_disco_cache); -- Handle disco requests to the server module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type ~= "get" then return; end local node = stanza.tags[1].attr.node; if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then return; end -- TODO fire event? local reply_query = get_server_disco_info(); reply_query.node = node; local reply = st.reply(stanza):add_child(reply_query); origin.send(reply); return true; end); module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type ~= "get" then return; end local node = stanza.tags[1].attr.node; if node and node ~= "" then return; end -- TODO fire event? local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); for jid, name in pairs(get_children(module.host)) do reply:tag("item", {jid = jid, name = name~=true and name or nil}):up(); end for _, item in ipairs(disco_items) do reply:tag("item", {jid=item[1], name=item[2]}):up(); end origin.send(reply); return true; end); -- Handle caps stream feature module:hook("stream-features", function (event) if event.origin.type == "c2s" then event.features:add_child(get_server_caps_feature()); end end); -- Handle disco requests to user accounts module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type ~= "get" then return; end local node = stanza.tags[1].attr.node; if node and node ~= "" then return; end -- TODO fire event? local username = jid_split(stanza.attr.to) or origin.username; if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info'}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account module:fire_event("account-disco-info", { origin = origin, stanza = reply }); origin.send(reply); return true; end end); module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type ~= "get" then return; end local node = stanza.tags[1].attr.node; if node and node ~= "" then return; end -- TODO fire event? local username = jid_split(stanza.attr.to) or origin.username; if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items'}); if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account module:fire_event("account-disco-items", { origin = origin, stanza = reply }); origin.send(reply); return true; end end); prosody-0.9.1/plugins/mod_saslauth.lua0000644000175000017500000002454712213321667020012 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local sm_bind_resource = require "core.sessionmanager".bind_resource; local sm_make_authenticated = require "core.sessionmanager".make_authenticated; local base64 = require "util.encodings".base64; local cert_verify_identity = require "util.x509".verify_identity; local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; local tostring = tostring; local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption"); local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth") local log = module._log; local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'; local function build_reply(status, ret, err_msg) local reply = st.stanza(status, {xmlns = xmlns_sasl}); if status == "challenge" then --log("debug", "CHALLENGE: %s", ret or ""); reply:text(base64.encode(ret or "")); elseif status == "failure" then reply:tag(ret):up(); if err_msg then reply:tag("text"):text(err_msg); end elseif status == "success" then --log("debug", "SUCCESS: %s", ret or ""); reply:text(base64.encode(ret or "")); else module:log("error", "Unknown sasl status: %s", status); end return reply; end local function handle_status(session, status, ret, err_msg) if status == "failure" then module:fire_event("authentication-failure", { session = session, condition = ret, text = err_msg }); session.sasl_handler = session.sasl_handler:clean_clone(); elseif status == "success" then local ok, err = sm_make_authenticated(session, session.sasl_handler.username); if ok then module:fire_event("authentication-success", { session = session }); session.sasl_handler = nil; session:reset_stream(); else module:log("warn", "SASL succeeded but username was invalid"); module:fire_event("authentication-failure", { session = session, condition = "not-authorized", text = err }); session.sasl_handler = session.sasl_handler:clean_clone(); return "failure", "not-authorized", "User authenticated successfully, but username was invalid"; end end return status, ret, err_msg; end local function sasl_process_cdata(session, stanza) local text = stanza[1]; if text then text = base64.decode(text); --log("debug", "AUTH: %s", text:gsub("[%z\001-\008\011\012\014-\031]", " ")); if not text then session.sasl_handler = nil; session.send(build_reply("failure", "incorrect-encoding")); return true; end end local status, ret, err_msg = session.sasl_handler:process(text); status, ret, err_msg = handle_status(session, status, ret, err_msg); local s = build_reply(status, ret, err_msg); log("debug", "sasl reply: %s", tostring(s)); session.send(s); return true; end module:hook_stanza(xmlns_sasl, "success", function (session, stanza) if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host); session.external_auth = "succeeded" session:reset_stream(); session:open_stream(session.from_host, session.to_host); module:fire_event("s2s-authenticated", { session = session, host = session.to_host }); return true; end) module:hook_stanza(xmlns_sasl, "failure", function (session, stanza) if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end module:log("info", "SASL EXTERNAL with %s failed", session.to_host) -- TODO: Log the failure reason session.external_auth = "failed" end, 500) module:hook_stanza(xmlns_sasl, "failure", function (session, stanza) -- TODO: Dialback wasn't loaded. Do something useful. end, 90) module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) if session.type ~= "s2sout_unauthed" or not session.secure then return; end local mechanisms = stanza:get_child("mechanisms", xmlns_sasl) if mechanisms then for mech in mechanisms:childtags() do if mech[1] == "EXTERNAL" then module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host); local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"}); reply:text(base64.encode(session.from_host)) session.sends2s(reply) session.external_auth = "attempting" return true end end end end, 150); local function s2s_external_auth(session, stanza) local mechanism = stanza.attr.mechanism; if not session.secure then if mechanism == "EXTERNAL" then session.sends2s(build_reply("failure", "encryption-required")) else session.sends2s(build_reply("failure", "invalid-mechanism")) end return true; end if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then session.sends2s(build_reply("failure", "invalid-mechanism")) return true; end local text = stanza[1] if not text then session.sends2s(build_reply("failure", "malformed-request")) return true end -- Either the value is "=" and we've already verified the external -- cert identity, or the value is a string and either matches the -- from_host ( text = base64.decode(text) if not text then session.sends2s(build_reply("failure", "incorrect-encoding")) return true; end if session.cert_identity_status == "valid" then if text ~= "" and text ~= session.from_host then session.sends2s(build_reply("failure", "invalid-authzid")) return true end else if text == "" then session.sends2s(build_reply("failure", "invalid-authzid")) return true end local cert = session.conn:socket():getpeercertificate() if (cert_verify_identity(text, "xmpp-server", cert)) then session.cert_identity_status = "valid" else session.cert_identity_status = "invalid" session.sends2s(build_reply("failure", "invalid-authzid")) return true end end session.external_auth = "succeeded" if not session.from_host then session.from_host = text; end session.sends2s(build_reply("success")) local domain = text ~= "" and text or session.from_host; module:log("info", "Accepting SASL EXTERNAL identity from %s", domain); module:fire_event("s2s-authenticated", { session = session, host = domain }); session:reset_stream(); return true end module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event) local session, stanza = event.origin, event.stanza; if session.type == "s2sin_unauthed" then return s2s_external_auth(session, stanza) end if session.type ~= "c2s_unauthed" then return; end if session.sasl_handler and session.sasl_handler.selected then session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one end if not session.sasl_handler then session.sasl_handler = usermanager_get_sasl_handler(module.host, session); end local mechanism = stanza.attr.mechanism; if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then session.send(build_reply("failure", "encryption-required")); return true; end local valid_mechanism = session.sasl_handler:select(mechanism); if not valid_mechanism then session.send(build_reply("failure", "invalid-mechanism")); return true; end return sasl_process_cdata(session, stanza); end); module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event) local session = event.origin; if not(session.sasl_handler and session.sasl_handler.selected) then session.send(build_reply("failure", "not-authorized", "Out of order SASL element")); return true; end return sasl_process_cdata(session, event.stanza); end); module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event) local session = event.origin; session.sasl_handler = nil; session.send(build_reply("failure", "aborted")); return true; end); local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' }; local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' }; local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' }; module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if not origin.username then if secure_auth_only and not origin.secure then return; end origin.sasl_handler = usermanager_get_sasl_handler(module.host, origin); local mechanisms = st.stanza("mechanisms", mechanisms_attr); for mechanism in pairs(origin.sasl_handler:mechanisms()) do if mechanism ~= "PLAIN" or origin.secure or allow_unencrypted_plain_auth then mechanisms:tag("mechanism"):text(mechanism):up(); end end if mechanisms[1] then features:add_child(mechanisms); end else features:tag("bind", bind_attr):tag("required"):up():up(); features:tag("session", xmpp_session_attr):tag("optional"):up():up(); end end); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; if origin.secure and origin.type == "s2sin_unauthed" then -- Offer EXTERNAL if chain is valid and either we didn't validate -- the identity or it passed. if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable module:log("debug", "Offering SASL EXTERNAL") features:tag("mechanisms", { xmlns = xmlns_sasl }) :tag("mechanism"):text("EXTERNAL") :up():up(); end end end); module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event) local origin, stanza = event.origin, event.stanza; local resource; if stanza.attr.type == "set" then local bind = stanza.tags[1]; resource = bind:child_with_name("resource"); resource = resource and #resource.tags == 0 and resource[1] or nil; end local success, err_type, err, err_msg = sm_bind_resource(origin, resource); if success then origin.send(st.reply(stanza) :tag("bind", { xmlns = xmlns_bind }) :tag("jid"):text(origin.full_jid)); origin.log("debug", "Resource bound: %s", origin.full_jid); else origin.send(st.error_reply(stanza, err_type, err, err_msg)); origin.log("debug", "Resource bind failed: %s", err_msg or err); end return true; end); local function handle_legacy_session(event) event.origin.send(st.reply(event.stanza)); return true; end module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session); module:hook("iq/host/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session); prosody-0.9.1/plugins/mod_storage_none.lua0000644000175000017500000000062012213321667020633 0ustar matthewmatthewlocal driver = {}; local driver_mt = { __index = driver }; function driver:open(store) return setmetatable({ store = store }, driver_mt); end function driver:get(user) return {}; end function driver:set(user, data) return nil, "Storage disabled"; end function driver:stores(username) return { "roster" }; end function driver:purge(user) return true; end module:provides("storage", driver); prosody-0.9.1/plugins/mod_welcome.lua0000644000175000017500000000130012213321667017577 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local welcome_text = module:get_option("welcome_message") or "Hello $username, welcome to the $host IM server!"; local st = require "util.stanza"; module:hook("user-registered", function (user) local welcome_stanza = st.message({ to = user.username.."@"..user.host, from = host }) :tag("body"):text(welcome_text:gsub("$(%w+)", user)); module:send(welcome_stanza); module:log("debug", "Welcomed user %s@%s", user.username, user.host); end); prosody-0.9.1/plugins/mod_auth_anonymous.lua0000644000175000017500000000347712213321667021236 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local new_sasl = require "util.sasl".new; local datamanager = require "util.datamanager"; local hosts = prosody.hosts; -- define auth provider local provider = {}; function provider.test_password(username, password) return nil, "Password based auth not supported."; end function provider.get_password(username) return nil, "Password not available."; end function provider.set_password(username, password) return nil, "Password based auth not supported."; end function provider.user_exists(username) return nil, "Only anonymous users are supported."; -- FIXME check if anonymous user is connected? end function provider.create_user(username, password) return nil, "Account creation/modification not supported."; end function provider.get_sasl_handler() local anonymous_authentication_profile = { anonymous = function(sasl, username, realm) return true; -- for normal usage you should always return true here end }; return new_sasl(module.host, anonymous_authentication_profile); end function provider.users() return next, hosts[host].sessions, nil; end -- datamanager callback to disable writes local function dm_callback(username, host, datastore, data) if host == module.host then return false; end return username, host, datastore, data; end if not module:get_option_boolean("allow_anonymous_s2s", false) then module:hook("route/remote", function (event) return false; -- Block outgoing s2s from anonymous users end, 300); end function module.load() datamanager.add_callback(dm_callback); end function module.unload() datamanager.remove_callback(dm_callback); end module:provides("auth", provider); prosody-0.9.1/plugins/mod_version.lua0000644000175000017500000000245012213321667017640 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; module:add_feature("jabber:iq:version"); local version; local query = st.stanza("query", {xmlns = "jabber:iq:version"}) :tag("name"):text("Prosody"):up() :tag("version"):text(prosody.version):up(); if not module:get_option("hide_os_type") then if os.getenv("WINDIR") then version = "Windows"; else local os_version_command = module:get_option("os_version_command"); local ok, pposix = pcall(require, "util.pposix"); if not os_version_command and (ok and pposix and pposix.uname) then version = pposix.uname().sysname; end if not version then local uname = io.popen(os_version_command or "uname"); if uname then version = uname:read("*a"); end uname:close(); end end if version then version = version:match("^%s*(.-)%s*$") or version; query:tag("os"):text(version):up(); end end module:hook("iq/host/jabber:iq:version:query", function(event) local stanza = event.stanza; if stanza.attr.type == "get" and stanza.attr.to == module.host then event.origin.send(st.reply(stanza):add_child(query)); return true; end end); prosody-0.9.1/plugins/mod_lastactivity.lua0000644000175000017500000000305512213321667020675 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; module:add_feature("jabber:iq:last"); local map = {}; module:hook("pre-presence/bare", function(event) local stanza = event.stanza; if not(stanza.attr.to) and stanza.attr.type == "unavailable" then local t = os.time(); local s = stanza:child_with_name("status"); s = s and #s.tags == 0 and s[1] or ""; map[event.origin.username] = {s = s, t = t}; end end, 10); module:hook("iq/bare/jabber:iq:last:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then local username = jid_split(stanza.attr.to) or origin.username; if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then local seconds, text = "0", ""; if map[username] then seconds = tostring(os.difftime(os.time(), map[username].t)); text = map[username].s; end origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=seconds}):text(text)); else origin.send(st.error_reply(stanza, 'auth', 'forbidden')); end return true; end end); module.save = function() return {map = map}; end module.restore = function(data) map = data.map or {}; end prosody-0.9.1/plugins/mod_storage_internal.lua0000644000175000017500000000127012213321667021512 0ustar matthewmatthewlocal datamanager = require "core.storagemanager".olddm; local host = module.host; local driver = {}; local driver_mt = { __index = driver }; function driver:open(store, typ) return setmetatable({ store = store, type = typ }, driver_mt); end function driver:get(user) return datamanager.load(user, host, self.store); end function driver:set(user, data) return datamanager.store(user, host, self.store, data); end function driver:stores(username) return datamanager.stores(username, host); end function driver:users() return datamanager.users(host, self.store, self.type); end function driver:purge(user) return datamanager.purge(user, host); end module:provides("storage", driver); prosody-0.9.1/plugins/sql.lib.lua0000644000175000017500000000026412213321667016661 0ustar matthewmatthewlocal 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; prosody-0.9.1/plugins/mod_roster.lua0000644000175000017500000001323512213321667017474 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local t_concat = table.concat; local tonumber = tonumber; local pairs, ipairs = pairs, ipairs; local rm_load_roster = require "core.rostermanager".load_roster; local rm_remove_from_roster = require "core.rostermanager".remove_from_roster; local rm_add_to_roster = require "core.rostermanager".add_to_roster; local rm_roster_push = require "core.rostermanager".roster_push; local core_post_stanza = prosody.core_post_stanza; module:add_feature("jabber:iq:roster"); local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}); module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if origin.username then features:add_child(rosterver_stream_feature); end end); module:hook("iq/self/jabber:iq:roster:query", function(event) local session, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then local roster = st.reply(stanza); local client_ver = tonumber(stanza.tags[1].attr.ver); local server_ver = tonumber(session.roster[false].version or 1); if not (client_ver and server_ver) or client_ver ~= server_ver then roster:query("jabber:iq:roster"); -- Client does not support versioning, or has stale roster for jid, item in pairs(session.roster) do if jid ~= "pending" and jid then roster:tag("item", { jid = jid, subscription = item.subscription, ask = item.ask, name = item.name, }); for group in pairs(item.groups) do roster:tag("group"):text(group):up(); end roster:up(); -- move out from item end end roster.tags[1].attr.ver = server_ver; end session.send(roster); session.interested = true; -- resource is interested in roster updates else -- stanza.attr.type == "set" local query = stanza.tags[1]; if #query.tags == 1 and query.tags[1].name == "item" and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid -- Protection against overwriting roster.pending, until we move it and query.tags[1].attr.jid ~= "pending" then local item = query.tags[1]; local from_node, from_host = jid_split(stanza.attr.from); local jid = jid_prep(item.attr.jid); local node, host, resource = jid_split(jid); if not resource and host then if jid ~= from_node.."@"..from_host then if item.attr.subscription == "remove" then local roster = session.roster; local r_item = roster[jid]; if r_item then local to_bare = node and (node.."@"..host) or host; -- bare JID if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare})); end if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare})); end local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid); if success then session.send(st.reply(stanza)); rm_roster_push(from_node, from_host, jid); else session.send(st.error_reply(stanza, err_type, err_cond, err_msg)); end else session.send(st.error_reply(stanza, "modify", "item-not-found")); end else local r_item = {name = item.attr.name, groups = {}}; if r_item.name == "" then r_item.name = nil; end if session.roster[jid] then r_item.subscription = session.roster[jid].subscription; r_item.ask = session.roster[jid].ask; else r_item.subscription = "none"; end for _, child in ipairs(item) do if child.name == "group" then local text = t_concat(child); if text and text ~= "" then r_item.groups[text] = true; end end end local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item); if success then -- Ok, send success session.send(st.reply(stanza)); -- and push change to all resources rm_roster_push(from_node, from_host, jid); else -- Adding to roster failed session.send(st.error_reply(stanza, err_type, err_cond, err_msg)); end end else -- Trying to add self to roster session.send(st.error_reply(stanza, "cancel", "not-allowed")); end else -- Invalid JID added to roster session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error? end else -- Roster set didn't include a single item, or its name wasn't 'item' session.send(st.error_reply(stanza, "modify", "bad-request")); end end return true; end); module:hook_global("user-deleted", function(event) local username, host = event.username, event.host; if host ~= module.host then return end local bare = username .. "@" .. host; local roster = rm_load_roster(username, host); for jid, item in pairs(roster) do if jid and jid ~= "pending" then if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then module:send(st.presence({type="unsubscribed", from=bare, to=jid})); end if item.subscription == "both" or item.subscription == "to" or item.ask then module:send(st.presence({type="unsubscribe", from=bare, to=jid})); end end end end, 300); prosody-0.9.1/plugins/mod_compression.lua0000644000175000017500000001624112213321667020517 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2009-2012 Tobias Markmann -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local zlib = require "zlib"; local pcall = pcall; local tostring = tostring; local xmlns_compression_feature = "http://jabber.org/features/compress" local xmlns_compression_protocol = "http://jabber.org/protocol/compress" local xmlns_stream = "http://etherx.jabber.org/streams"; local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up(); local add_filter = require "util.filters".add_filter; local compression_level = module:get_option_number("compression_level", 7); if not compression_level or compression_level < 1 or compression_level > 9 then module:log("warn", "Invalid compression level in config: %s", tostring(compression_level)); module:log("warn", "Module loading aborted. Compression won't be available."); return; end module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then -- FIXME only advertise compression support when TLS layer has no compression enabled features:add_child(compression_stream_feature); end end); module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; -- FIXME only advertise compression support when TLS layer has no compression enabled if not origin.compressed and (origin.type == "c2s" or origin.type == "s2sin" or origin.type == "s2sout") then features:add_child(compression_stream_feature); end end); -- Hook to activate compression if remote server supports it. module:hook_stanza(xmlns_stream, "features", function (session, stanza) if not session.compressed and (session.type == "c2s" or session.type == "s2sin" or session.type == "s2sout") then -- does remote server support compression? local comp_st = stanza:child_with_name("compression"); if comp_st then -- do we support the mechanism for a in comp_st:children() do local algorithm = a[1] if algorithm == "zlib" then session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib")) session.log("debug", "Enabled compression using zlib.") return true; end end session.log("debug", "Remote server supports no compression algorithm we support.") end end end , 250); -- returns either nil or a fully functional ready to use inflate stream local function get_deflate_stream(session) local status, deflate_stream = pcall(zlib.deflate, compression_level); if status == false then local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); (session.sends2s or session.send)(error_st); session.log("error", "Failed to create zlib.deflate filter."); module:log("error", "%s", tostring(deflate_stream)); return end return deflate_stream end -- returns either nil or a fully functional ready to use inflate stream local function get_inflate_stream(session) local status, inflate_stream = pcall(zlib.inflate); if status == false then local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); (session.sends2s or session.send)(error_st); session.log("error", "Failed to create zlib.inflate filter."); module:log("error", "%s", tostring(inflate_stream)); return end return inflate_stream end -- setup compression for a stream local function setup_compression(session, deflate_stream) add_filter(session, "bytes/out", function(t) local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync'); if status == false then module:log("warn", "%s", tostring(compressed)); session:close({ condition = "undefined-condition"; text = compressed; extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); }); return; end return compressed; end); end -- setup decompression for a stream local function setup_decompression(session, inflate_stream) add_filter(session, "bytes/in", function(data) local status, decompressed, eof = pcall(inflate_stream, data); if status == false then module:log("warn", "%s", tostring(decompressed)); session:close({ condition = "undefined-condition"; text = decompressed; extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); }); return; end return decompressed; end); end module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event) local session = event.origin; if session.type == "s2sout_unauthed" or session.type == "s2sout" then session.log("debug", "Activating compression...") -- create deflate and inflate streams local deflate_stream = get_deflate_stream(session); if not deflate_stream then return true; end local inflate_stream = get_inflate_stream(session); if not inflate_stream then return true; end -- setup compression for session.w setup_compression(session, deflate_stream); -- setup decompression for session.data setup_decompression(session, inflate_stream); session:reset_stream(); session:open_stream(session.from_host, session.to_host); session.compressed = true; return true; end end); module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event) local session, stanza = event.origin, event.stanza; if session.type == "c2s" or session.type == "s2sin" or session.type == "c2s_unauthed" or session.type == "s2sin_unauthed" then -- fail if we are already compressed if session.compressed then local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"); (session.sends2s or session.send)(error_st); session.log("debug", "Client tried to establish another compression layer."); return true; end -- checking if the compression method is supported local method = stanza:child_with_name("method"); method = method and (method[1] or ""); if method == "zlib" then session.log("debug", "zlib compression enabled."); -- create deflate and inflate streams local deflate_stream = get_deflate_stream(session); if not deflate_stream then return true; end local inflate_stream = get_inflate_stream(session); if not inflate_stream then return true; end (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol})); session:reset_stream(); -- setup compression for session.w setup_compression(session, deflate_stream); -- setup decompression for session.data setup_decompression(session, inflate_stream); session.compressed = true; elseif method then session.log("debug", "%s compression selected, but we don't support it.", tostring(method)); local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method"); (session.sends2s or session.send)(error_st); else (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed")); end return true; end end); prosody-0.9.1/plugins/mod_message.lua0000644000175000017500000000443212213321667017601 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local full_sessions = prosody.full_sessions; local bare_sessions = prosody.bare_sessions; local st = require "util.stanza"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local user_exists = require "core.usermanager".user_exists; local function process_to_bare(bare, origin, stanza) local user = bare_sessions[bare]; local t = stanza.attr.type; if t == "error" then -- discard elseif t == "groupchat" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); elseif t == "headline" then if user and stanza.attr.to == bare then for _, session in pairs(user.sessions) do if session.presence and session.priority >= 0 then session.send(stanza); end end end -- current policy is to discard headlines if no recipient is available else -- chat or normal message if user then -- some resources are connected local recipients = user.top_resources; if recipients then local sent; for i=1,#recipients do sent = recipients[i].send(stanza) or sent; end if sent then return true; end end end -- no resources are online local node, host = jid_split(bare); local ok if user_exists(node, host) then -- TODO apply the default privacy list ok = module:fire_event('message/offline/handle', { origin = origin, stanza = stanza, }); end if not ok then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end end return true; end module:hook("message/full", function(data) -- message to full JID recieved local origin, stanza = data.origin, data.stanza; local session = full_sessions[stanza.attr.to]; if session and session.send(stanza) then return true; else -- resource not online return process_to_bare(jid_bare(stanza.attr.to), origin, stanza); end end); module:hook("message/bare", function(data) -- message to bare JID recieved local origin, stanza = data.origin, data.stanza; return process_to_bare(stanza.attr.to or (origin.username..'@'..origin.host), origin, stanza); end); module:add_feature("msgoffline"); prosody-0.9.1/plugins/mod_pep.lua0000644000175000017500000002311112213321667016734 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local st = require "util.stanza"; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local pairs = pairs; local next = next; local type = type; local calculate_hash = require "util.caps".calculate_hash; local core_post_stanza = prosody.core_post_stanza; local NULL = {}; local data = {}; local recipients = {}; local hash_map = {}; module.save = function() return { data = data, recipients = recipients, hash_map = hash_map }; end module.restore = function(state) data = state.data or {}; recipients = state.recipients or {}; hash_map = state.hash_map or {}; end module:add_identity("pubsub", "pep", module:get_option_string("name", "Prosody")); module:add_feature("http://jabber.org/protocol/pubsub#publish"); local function subscription_presence(user_bare, recipient) local recipient_bare = jid_bare(recipient); if (recipient_bare == user_bare) then return true end local username, host = jid_split(user_bare); return is_contact_subscribed(username, host, recipient_bare); end local function publish(session, node, id, item) item.attr.xmlns = nil; local disable = #item.tags ~= 1 or #item.tags[1] == 0; if #item.tags == 0 then item.name = "retract"; end local bare = session.username..'@'..session.host; local stanza = st.message({from=bare, type='headline'}) :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'}) :tag('items', {node=node}) :add_child(item) :up() :up(); -- store for the future local user_data = data[bare]; if disable then if user_data then user_data[node] = nil; if not next(user_data) then data[bare] = nil; end end else if not user_data then user_data = {}; data[bare] = user_data; end user_data[node] = {id, item}; end -- broadcast for recipient, notify in pairs(recipients[bare] or NULL) do if notify[node] then stanza.attr.to = recipient; core_post_stanza(session, stanza); end end end local function publish_all(user, recipient, session) local d = data[user]; local notify = recipients[user] and recipients[user][recipient]; if d and notify then for node in pairs(notify) do if d[node] then local id, item = unpack(d[node]); session.send(st.message({from=user, to=recipient, type='headline'}) :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'}) :tag('items', {node=node}) :add_child(item) :up() :up()); end end end end local function get_caps_hash_from_presence(stanza, current) local t = stanza.attr.type; if not t then for _, child in pairs(stanza.tags) do if child.name == "c" and child.attr.xmlns == "http://jabber.org/protocol/caps" then local attr = child.attr; if attr.hash then -- new caps if attr.hash == 'sha-1' and attr.node and attr.ver then return attr.ver, attr.node.."#"..attr.ver; end else -- legacy caps if attr.node and attr.ver then return attr.node.."#"..attr.ver.."#"..(attr.ext or ""), attr.node.."#"..attr.ver; end end return; -- bad caps format end end elseif t == "unavailable" or t == "error" then return; end return current; -- no caps, could mean caps optimization, so return current end module:hook("presence/bare", function(event) -- inbound presence to bare JID recieved local origin, stanza = event.origin, event.stanza; local user = stanza.attr.to or (origin.username..'@'..origin.host); local t = stanza.attr.type; local self = not stanza.attr.to; if not t then -- available presence if self or subscription_presence(user, stanza.attr.from) then local recipient = stanza.attr.from; local current = recipients[user] and recipients[user][recipient]; local hash = get_caps_hash_from_presence(stanza, current); if current == hash or (current and current == hash_map[hash]) then return; end if not hash then if recipients[user] then recipients[user][recipient] = nil; end else recipients[user] = recipients[user] or {}; if hash_map[hash] then recipients[user][recipient] = hash_map[hash]; publish_all(user, recipient, origin); else recipients[user][recipient] = hash; local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host; if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then -- COMPAT from ~= stanza.attr.to because OneTeam and Asterisk 1.8 can't deal with missing from attribute origin.send( st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"}) :query("http://jabber.org/protocol/disco#info") ); end end end end elseif t == "unavailable" then if recipients[user] then recipients[user][stanza.attr.from] = nil; end elseif not self and t == "unsubscribe" then local from = jid_bare(stanza.attr.from); local subscriptions = recipients[user]; if subscriptions then for subscriber in pairs(subscriptions) do if jid_bare(subscriber) == from then recipients[user][subscriber] = nil; end end end end end, 10); module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event) local session, stanza = event.origin, event.stanza; local payload = stanza.tags[1]; if stanza.attr.type == 'set' and (not stanza.attr.to or jid_bare(stanza.attr.from) == stanza.attr.to) then payload = payload.tags[1]; if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- local node = payload.attr.node; payload = payload.tags[1]; if payload and payload.name == "item" then -- local id = payload.attr.id or "1"; payload.attr.id = id; session.send(st.reply(stanza)); publish(session, node, id, st.clone(payload)); return true; end end elseif stanza.attr.type == 'get' then local user = stanza.attr.to and jid_bare(stanza.attr.to) or session.username..'@'..session.host; if subscription_presence(user, stanza.attr.from) then local user_data = data[user]; local node, requested_id; payload = payload.tags[1]; if payload and payload.name == 'items' then node = payload.attr.node; local item = payload.tags[1]; if item and item.name == "item" then requested_id = item.attr.id; end end if node and user_data and user_data[node] then -- Send the last item local id, item = unpack(user_data[node]); if not requested_id or id == requested_id then local stanza = st.reply(stanza) :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'}) :tag('items', {node=node}) :add_child(item) :up() :up(); session.send(stanza); return true; else -- requested item doesn't exist local stanza = st.reply(stanza) :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'}) :tag('items', {node=node}) :up(); session.send(stanza); return true; end elseif node then -- node doesn't exist session.send(st.error_reply(stanza, 'cancel', 'item-not-found')); return true; else --invalid request session.send(st.error_reply(stanza, 'modify', 'bad-request')); return true; end else --no presence subscription session.send(st.error_reply(stanza, 'auth', 'not-authorized') :tag('presence-subscription-required', {xmlns='http://jabber.org/protocol/pubsub#errors'})); return true; end end end); module:hook("iq-result/bare/disco", function(event) local session, stanza = event.origin, event.stanza; if stanza.attr.type == "result" then local disco = stanza.tags[1]; if disco and disco.name == "query" and disco.attr.xmlns == "http://jabber.org/protocol/disco#info" then -- Process disco response local self = not stanza.attr.to; local user = stanza.attr.to or (session.username..'@'..session.host); local contact = stanza.attr.from; local current = recipients[user] and recipients[user][contact]; if type(current) ~= "string" then return; end -- check if waiting for recipient's response local ver = current; if not string.find(current, "#") then ver = calculate_hash(disco.tags); -- calculate hash end local notify = {}; for _, feature in pairs(disco.tags) do if feature.name == "feature" and feature.attr.var then local nfeature = feature.attr.var:match("^(.*)%+notify$"); if nfeature then notify[nfeature] = true; end end end hash_map[ver] = notify; -- update hash map if self then for jid, item in pairs(session.roster) do -- for all interested contacts if item.subscription == "both" or item.subscription == "from" then if not recipients[jid] then recipients[jid] = {}; end recipients[jid][contact] = notify; publish_all(jid, contact, session); end end end recipients[user][contact] = notify; -- set recipient's data to calculated data -- send messages to recipient publish_all(user, contact, session); end end end); module:hook("account-disco-info", function(event) local stanza = event.stanza; stanza:tag('identity', {category='pubsub', type='pep'}):up(); stanza:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up(); end); module:hook("account-disco-items", function(event) local stanza = event.stanza; local bare = stanza.attr.to; local user_data = data[bare]; if user_data then for node, _ in pairs(user_data) do stanza:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes end end end); prosody-0.9.1/plugins/mod_proxy65.lua0000644000175000017500000001717512213321667017521 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2011 Matthew Wild -- Copyright (C) 2008-2011 Waqas Hussain -- Copyright (C) 2009 Thilo Cestonaro -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep; local st = require "util.stanza"; local sha1 = require "util.hashes".sha1; local b64 = require "util.encodings".base64.encode; local server = require "net.server"; local portmanager = require "core.portmanager"; local sessions, transfers = module:shared("sessions", "transfers"); local max_buffer_size = 4096; local listener = {}; function listener.onincoming(conn, data) local session = sessions[conn] or {}; local transfer = transfers[session.sha]; if transfer and transfer.activated then -- copy data between initiator and target local initiator, target = transfer.initiator, transfer.target; (conn == initiator and target or initiator):write(data); return; end -- FIXME server.link should be doing this? if not session.greeting_done then local nmethods = data:byte(2) or 0; if data:byte(1) == 0x05 and nmethods > 0 and #data == 2 + nmethods then -- check if we have all the data if data:find("%z") then -- 0x00 = 'No authentication' is supported session.greeting_done = true; sessions[conn] = session; conn:write("\5\0"); -- send (SOCKS version 5, No authentication) module:log("debug", "SOCKS5 greeting complete"); return; end end -- else error, unexpected input conn:write("\5\255"); -- send (SOCKS version 5, no acceptable method) conn:close(); module:log("debug", "Invalid SOCKS5 greeting recieved: '%s'", b64(data)); else -- connection request --local head = string.char( 0x05, 0x01, 0x00, 0x03, 40 ); -- ( VER=5=SOCKS5, CMD=1=CONNECT, RSV=0=RESERVED, ATYP=3=DOMAIMNAME, SHA-1 size ) if #data == 47 and data:sub(1,5) == "\5\1\0\3\40" and data:sub(-2) == "\0\0" then local sha = data:sub(6, 45); conn:pause(); conn:write("\5\0\0\3\40" .. sha .. "\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) if not transfers[sha] then transfers[sha] = {}; transfers[sha].target = conn; session.sha = sha; module:log("debug", "SOCKS5 target connected for session %s", sha); else -- transfers[sha].target ~= nil transfers[sha].initiator = conn; session.sha = sha; module:log("debug", "SOCKS5 initiator connected for session %s", sha); server.link(conn, transfers[sha].target, max_buffer_size); server.link(transfers[sha].target, conn, max_buffer_size); end else -- error, unexpected input conn:write("\5\1\0\3\0\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) conn:close(); module:log("debug", "Invalid SOCKS5 negotiation recieved: '%s'", b64(data)); end end end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then if transfers[session.sha] then local initiator, target = transfers[session.sha].initiator, transfers[session.sha].target; if initiator == conn and target ~= nil then target:close(); elseif target == conn and initiator ~= nil then initiator:close(); end transfers[session.sha] = nil; end -- Clean up any session-related stuff here sessions[conn] = nil; end end function module.add_host(module) local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service"); local proxy_address = module:get_option("proxy65_address", host); local proxy_port = next(portmanager.get_active_services():search("proxy65", nil)[1] or {}); local proxy_acl = module:get_option("proxy65_acl"); -- COMPAT w/pre-0.9 where proxy65_port was specified in the components section of the config local legacy_config = module:get_option_number("proxy65_port"); if legacy_config then module:log("warn", "proxy65_port is deprecated, please put proxy65_ports = { %d } into the global section instead", legacy_config); end module:add_identity("proxy", "bytestreams", name); module:add_feature("http://jabber.org/protocol/bytestreams"); module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event) local origin, stanza = event.origin, event.stanza; if not stanza.tags[1].attr.node then origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info") :tag("identity", {category='proxy', type='bytestreams', name=name}):up() :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) ); return true; end end, -1); module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event) local origin, stanza = event.origin, event.stanza; if not stanza.tags[1].attr.node then origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items")); return true; end end, -1); module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event) local origin, stanza = event.origin, event.stanza; -- check ACL while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it local jid = stanza.attr.from; local allow; for _, acl in ipairs(proxy_acl) do if jid_compare(jid, acl) then allow = true; break; end end if allow then break; end module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from)); origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local sid = stanza.tags[1].attr.sid; origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid}) :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port})); return true; end); module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event) local origin, stanza = event.origin, event.stanza; local query = stanza.tags[1]; local sid = query.attr.sid; local from = stanza.attr.from; local to = query:get_child_text("activate"); local prepped_to = jid_prep(to); local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to); if prepped_to and sid then local sha = sha1(sid .. from .. prepped_to, true); if not transfers[sha] then module:log("debug", "Activation request has unknown session id; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "item-not-found")); elseif not transfers[sha].initiator then module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info); origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy")); --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info); -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy")); else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then module:log("debug", "Transfer activated (%s)", info); transfers[sha].activated = true; transfers[sha].target:resume(); transfers[sha].initiator:resume(); origin.send(st.reply(stanza)); end elseif to and sid then module:log("debug", "Malformed activation jid; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "jid-malformed")); else module:log("debug", "Bad request; activation failed (%s)", info); origin.send(st.error_reply(stanza, "modify", "bad-request")); end return true; end); end module:provides("net", { default_port = 5000; listener = listener; multiplex = { pattern = "^\5"; }; }); prosody-0.9.1/plugins/mod_bosh.lua0000644000175000017500000003705712213321667017121 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); -- Global module local hosts = _G.hosts; local new_xmpp_stream = require "util.xmppstream".new; local sm = require "core.sessionmanager"; local sm_destroy_session = sm.destroy_session; local new_uuid = require "util.uuid".generate; local fire_event = prosody.events.fire_event; local core_process_stanza = prosody.core_process_stanza; local st = require "util.stanza"; local logger = require "util.logger"; local log = logger.init("mod_bosh"); local initialize_filters = require "util.filters".initialize; local math_min = math.min; local xpcall, tostring, type = xpcall, tostring, type; local traceback = debug.traceback; local xmlns_streams = "http://etherx.jabber.org/streams"; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send) local stream_callbacks = { stream_ns = xmlns_bosh, stream_tag = "body", default_ns = "jabber:client" }; local BOSH_DEFAULT_HOLD = module:get_option_number("bosh_default_hold", 1); local BOSH_DEFAULT_INACTIVITY = module:get_option_number("bosh_max_inactivity", 60); local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5); local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2); local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" }; local cross_domain = module:get_option("cross_domain_bosh", false); if cross_domain then default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"; default_headers["Access-Control-Allow-Headers"] = "Content-Type"; default_headers["Access-Control-Max-Age"] = "7200"; if cross_domain == true then default_headers["Access-Control-Allow-Origin"] = "*"; elseif type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end if type(cross_domain) == "string" then default_headers["Access-Control-Allow-Origin"] = cross_domain; end end local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items; local function get_ip_from_request(request) local ip = request.conn:ip(); local forwarded_for = request.headers.x_forwarded_for; if forwarded_for then forwarded_for = forwarded_for..", "..ip; for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do if not trusted_proxies[forwarded_ip] then ip = forwarded_ip; end end end return ip; end local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local os_time = os.time; -- All sessions, and sessions that have no requests open local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions"); -- Used to respond to idle sessions (those with waiting requests) local waiting_requests = {}; function on_destroy_request(request) log("debug", "Request destroyed: %s", tostring(request)); waiting_requests[request] = nil; local session = sessions[request.context.sid]; if session then local requests = session.requests; for i, r in ipairs(requests) do if r == request then t_remove(requests, i); break; end end -- If this session now has no requests open, mark it as inactive local max_inactive = session.bosh_max_inactive; if max_inactive and #requests == 0 then inactive_sessions[session] = os_time() + max_inactive; (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); end end end function handle_OPTIONS(request) local headers = {}; for k,v in pairs(default_headers) do headers[k] = v; end headers["Content-Type"] = nil; return { headers = headers, body = "" }; end function handle_POST(event) log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); local request, response = event.request, event.response; response.on_destroy = on_destroy_request; local body = request.body; local context = { request = request, response = response, notopen = true }; local stream = new_xmpp_stream(context, stream_callbacks); response.context = context; -- stream:feed() calls the stream_callbacks, so all stanzas in -- the body are processed in this next line before it returns. -- In particular, the streamopened() stream callback is where -- much of the session logic happens, because it's where we first -- get to see the 'sid' of this request. stream:feed(body); -- Stanzas (if any) in the request have now been processed, and -- we take care of the high-level BOSH logic here, including -- giving a response or putting the request "on hold". local session = sessions[context.sid]; if session then -- Session was marked as inactive, since we have -- a request open now, unmark it if inactive_sessions[session] and #session.requests > 0 then inactive_sessions[session] = nil; end local r = session.requests; log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold); log("debug", "and there are %d things in the send_buffer:", #session.send_buffer); for i, thing in ipairs(session.send_buffer) do log("debug", " %s", tostring(thing)); end if #r > session.bosh_hold then -- We are holding too many requests, send what's in the buffer, log("debug", "We are holding too many requests, so..."); if #session.send_buffer > 0 then log("debug", "...sending what is in the buffer") session.send(t_concat(session.send_buffer)); session.send_buffer = {}; else -- or an empty response log("debug", "...sending an empty response"); session.send(""); end elseif #session.send_buffer > 0 then log("debug", "Session has data in the send buffer, will send now.."); local resp = t_concat(session.send_buffer); session.send_buffer = {}; session.send(resp); end if not response.finished then -- We're keeping this request open, to respond later log("debug", "Have nothing to say, so leaving request unanswered for now"); if session.bosh_wait then waiting_requests[response] = os_time() + session.bosh_wait; end end if session.bosh_terminate then session.log("debug", "Closing session with %d requests open", #session.requests); session:close(); return nil; else return true; -- Inform http server we shall reply later end end end local function bosh_reset_stream(session) session.notopen = true; end local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; local function bosh_close_stream(session, reason) (session.log or log)("info", "BOSH client disconnected"); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams }); if reason then close_reply.attr.condition = "remote-stream-error"; if type(reason) == "string" then -- assume stream error close_reply:tag("stream:error") :tag(reason, {xmlns = xmlns_xmpp_streams}); elseif type(reason) == "table" then if reason.condition then close_reply:tag("stream:error") :tag(reason.condition, stream_xmlns_attr):up(); if reason.text then close_reply:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then close_reply:add_child(reason.extra); end elseif reason.name then -- a stanza close_reply = reason; end end log("info", "Disconnecting client, is: %s", tostring(close_reply)); end local response_body = tostring(close_reply); for _, held_request in ipairs(session.requests) do held_request.headers = default_headers; held_request:send(response_body); end sessions[session.sid] = nil; inactive_sessions[session] = nil; sm_destroy_session(session); end -- Handle the tag in the request payload. function stream_callbacks.streamopened(context, attr) local request, response = context.request, context.response; local sid = attr.sid; log("debug", "BOSH body open (sid: %s)", sid or ""); if not sid then -- New session request context.notopen = nil; -- Signals that we accept this opening tag -- TODO: Sanity checks here (rid, to, known host, etc.) if not hosts[attr.to] then -- Unknown host log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to)); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "host-unknown" }); response:send(tostring(close_reply)); return; end -- New session sid = new_uuid(); local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to, bosh_version = attr.ver, bosh_wait = math_min(attr.wait, bosh_max_wait), streamid = sid, bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true, log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure, ip = get_ip_from_request(request); }; sessions[sid] = session; local filter = initialize_filters(session); session.log("debug", "BOSH session created for request from %s", session.ip); log("info", "New BOSH session, assigned it sid '%s'", sid); -- Send creation response local creating_session = true; local r = session.requests; function session.send(s) -- We need to ensure that outgoing stanzas have the jabber:client xmlns if s.attr and not s.attr.xmlns then s = st.clone(s); s.attr.xmlns = "jabber:client"; end s = filter("stanzas/out", s); --log("debug", "Sending BOSH data: %s", tostring(s)); t_insert(session.send_buffer, tostring(s)); local oldest_request = r[1]; if oldest_request and not session.bosh_processing then log("debug", "We have an open request, so sending on that"); oldest_request.headers = default_headers; local body_attr = { xmlns = "http://jabber.org/protocol/httpbind", ["xmlns:stream"] = "http://etherx.jabber.org/streams"; type = session.bosh_terminate and "terminate" or nil; sid = sid; }; if creating_session then creating_session = nil; body_attr.inactivity = tostring(BOSH_DEFAULT_INACTIVITY); body_attr.polling = tostring(BOSH_DEFAULT_POLLING); body_attr.requests = tostring(BOSH_DEFAULT_REQUESTS); body_attr.wait = tostring(session.bosh_wait); body_attr.hold = tostring(session.bosh_hold); body_attr.authid = sid; body_attr.secure = "true"; body_attr.ver = '1.6'; body_attr.from = session.host; body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh"; body_attr["xmpp:version"] = "1.0"; end oldest_request:send(st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer)..""); session.send_buffer = {}; end return true; end request.sid = sid; end local session = sessions[sid]; if not session then -- Unknown sid log("info", "Client tried to use sid '%s' which we don't know about", sid); response.headers = default_headers; response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); context.notopen = nil; return; end if session.rid then local rid = tonumber(attr.rid); local diff = rid - session.rid; if diff > 1 then session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid); elseif diff <= 0 then -- Repeated, ignore session.log("debug", "rid repeated, ignoring: %s (diff %d)", session.rid, diff); context.notopen = nil; context.ignore = true; context.sid = sid; t_insert(session.requests, response); return; end session.rid = rid; end if attr.type == "terminate" then -- Client wants to end this session, which we'll do -- after processing any stanzas in this request session.bosh_terminate = true; end context.notopen = nil; -- Signals that we accept this opening tag t_insert(session.requests, response); context.sid = sid; session.bosh_processing = true; -- Used to suppress replies until processing of this request is done if session.notopen then local features = st.stanza("stream:features"); hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); fire_event("stream-features", session, features); session.send(tostring(features)); session.notopen = nil; end end local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(context, stanza) if context.ignore then return; end log("debug", "BOSH stanza received: %s\n", stanza:top_tag()); local session = sessions[context.sid]; if session then if stanza.attr.xmlns == xmlns_bosh then stanza.attr.xmlns = nil; end stanza = session.filter("stanzas/in", stanza); if stanza then return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); end end end function stream_callbacks.streamclosed(request) local session = sessions[request.sid]; if session then session.bosh_processing = false; if #session.send_buffer > 0 then session.send(""); end end end function stream_callbacks.error(context, error) log("debug", "Error parsing BOSH request payload; %s", error); if not context.sid then local response = context.response; response.headers = default_headers; response.status_code = 400; response:send(); return; end local session = sessions[context.sid]; if error == "stream-error" then -- Remote stream error, we close normally session:close(); else session:close({ condition = "bad-format", text = "Error processing stream" }); end end local dead_sessions = {}; function on_timer() -- log("debug", "Checking for requests soon to timeout..."); -- Identify requests timing out within the next few seconds local now = os_time() + 3; for request, reply_before in pairs(waiting_requests) do if reply_before <= now then log("debug", "%s was soon to timeout (at %d, now %d), sending empty response", tostring(request), reply_before, now); -- Send empty response to let the -- client know we're still here if request.conn then sessions[request.context.sid].send(""); end end end now = now - 3; local n_dead_sessions = 0; for session, close_after in pairs(inactive_sessions) do if close_after < now then (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now); sessions[session.sid] = nil; inactive_sessions[session] = nil; n_dead_sessions = n_dead_sessions + 1; dead_sessions[n_dead_sessions] = session; end end for i=1,n_dead_sessions do local session = dead_sessions[i]; dead_sessions[i] = nil; sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); end return 1; end module:add_timer(1, on_timer); local GET_response = { headers = { content_type = "text/html"; }; body = [[

It works! Now point your BOSH client to this URL to connect to Prosody.

For more information see Prosody: Setting up BOSH.

]]; }; function module.add_host(module) module:depends("http"); module:provides("http", { default_path = "/http-bind"; route = { ["GET"] = GET_response; ["GET /"] = GET_response; ["OPTIONS"] = handle_OPTIONS; ["OPTIONS /"] = handle_OPTIONS; ["POST"] = handle_POST; ["POST /"] = handle_POST; }; }); end prosody-0.9.1/plugins/mod_auth_cyrus.lua0000644000175000017500000000457012213321667020346 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = require "util.logger".init("auth_cyrus"); local usermanager_user_exists = require "core.usermanager".user_exists; local cyrus_service_realm = module:get_option("cyrus_service_realm"); local cyrus_service_name = module:get_option("cyrus_service_name"); local cyrus_application_name = module:get_option("cyrus_application_name"); local require_provisioning = module:get_option("cyrus_require_provisioning") or false; local host_fqdn = module:get_option("cyrus_server_fqdn"); prosody.unlock_globals(); --FIXME: Figure out why this is needed and -- why cyrussasl isn't caught by the sandbox local cyrus_new = require "util.sasl_cyrus".new; prosody.lock_globals(); local new_sasl = function(realm) return cyrus_new( cyrus_service_realm or realm, cyrus_service_name or "xmpp", cyrus_application_name or "prosody", host_fqdn ); end do -- diagnostic local list; for mechanism in pairs(new_sasl(module.host):mechanisms()) do list = (not(list) and mechanism) or (list..", "..mechanism); end if not list then module:log("error", "No Cyrus SASL mechanisms available"); else module:log("debug", "Available Cyrus SASL mechanisms: %s", list); end end local host = module.host; -- define auth provider local provider = {}; log("debug", "initializing default authentication provider for host '%s'", host); function provider.test_password(username, password) return nil, "Legacy auth not supported with Cyrus SASL."; end function provider.get_password(username) return nil, "Passwords unavailable for Cyrus SASL."; end function provider.set_password(username, password) return nil, "Passwords unavailable for Cyrus SASL."; end function provider.user_exists(username) if require_provisioning then return usermanager_user_exists(username, host); end return true; end function provider.create_user(username, password) return nil, "Account creation/modification not available with Cyrus SASL."; end function provider.get_sasl_handler() local handler = new_sasl(host); if require_provisioning then function handler.require_provisioning(username) return usermanager_user_exists(username, host); end end return handler; end module:provides("auth", provider); prosody-0.9.1/plugins/mod_announce.lua0000644000175000017500000000605612213321667017767 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st, jid = require "util.stanza", require "util.jid"; local hosts = prosody.hosts; local is_admin = require "core.usermanager".is_admin; function send_to_online(message, host) local sessions; if host then sessions = { [host] = hosts[host] }; else sessions = hosts; end local c = 0; for hostname, host_session in pairs(sessions) do if host_session.sessions then message.attr.from = hostname; for username in pairs(host_session.sessions) do c = c + 1; message.attr.to = username.."@"..hostname; module:send(message); end end end return c; end -- Old -based jabberd-style announcement sending function handle_announcement(event) local origin, stanza = event.origin, event.stanza; local node, host, resource = jid.split(stanza.attr.to); if resource ~= "announce/online" then return; -- Not an announcement end if not is_admin(stanza.attr.from) then -- Not an admin? Not allowed! module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from); return; end module:log("info", "Sending server announcement to all online users"); local message = st.clone(stanza); message.attr.type = "headline"; message.attr.from = host; local c = send_to_online(message, host); module:log("info", "Announcement sent to %d online users", c); return true; end module:hook("message/host", handle_announcement); -- Ad-hoc command (XEP-0133) local dataforms_new = require "util.dataforms".new; local announce_layout = dataforms_new{ title = "Making an Announcement"; instructions = "Fill out this form to make an announcement to all\nactive users of this service."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "subject", type = "text-single", label = "Subject" }; { name = "announcement", type = "text-multi", required = true, label = "Announcement" }; }; function announce_handler(self, data, state) if state then if data.action == "cancel" then return { status = "canceled" }; end local fields = announce_layout:data(data.form); module:log("info", "Sending server announcement to all online users"); local message = st.message({type = "headline"}, fields.announcement):up() :tag("subject"):text(fields.subject or "Announcement"); local count = send_to_online(message, data.to); module:log("info", "Announcement sent to %d online users", count); return { status = "completed", info = ("Announcement sent to %d online users"):format(count) }; else return { status = "executing", actions = {"next", "complete", default = "complete"}, form = announce_layout }, "executing"; end return true; end local adhoc_new = module:require "adhoc".new; local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin"); module:provides("adhoc", announce_desc); prosody-0.9.1/plugins/mod_storage_sql.lua0000644000175000017500000003207212213321667020501 0ustar matthewmatthew --[[ DB Tables: Prosody - key-value, map | host | user | store | key | type | value | ProsodyArchive - list | host | user | store | key | time | stanzatype | jsonvalue | Mapping: Roster - Prosody | host | user | "roster" | "contactjid" | type | value | | host | user | "roster" | NULL | "json" | roster[false] data | Account - Prosody | host | user | "accounts" | "username" | type | value | Offline - ProsodyArchive | host | user | "offline" | "contactjid" | time | "message" | json|XML | ]] local type = type; local tostring = tostring; local tonumber = tonumber; local pairs = pairs; local next = next; local setmetatable = setmetatable; local xpcall = xpcall; local json = require "util.json"; local build_url = require"socket.url".build; local DBI; local connection; local host,user,store = module.host; local params = module:get_option("sql"); local dburi; local connections = module:shared "/*/sql/connection-cache"; 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 resolve_relative_path = require "core.configmanager".resolve_relative_path; local function test_connection() if not connection then return nil; end if connection:ping() then return true; else module:log("debug", "Database connection closed"); connection = nil; connections[dburi] = nil; end end local function connect() if not test_connection() then prosody.unlock_globals(); local dbh, err = DBI.Connect( params.driver, params.database, params.username, params.password, params.host, params.port ); prosody.lock_globals(); if not dbh then module:log("debug", "Database connection failed: %s", tostring(err)); return nil, err; end module:log("debug", "Successfully connected to database"); dbh:autocommit(false); -- don't commit automatically connection = dbh; connections[dburi] = dbh; end return connection; end local function create_table() 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"); end local stmt, err = connection:prepare(create_sql); if stmt then local ok = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then module:log("info", "Initialized new %s database with prosody table", params.driver); 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 stmt, err = connection:prepare(index_sql); local ok, commit_ok, commit_err; if stmt then ok, err = stmt:execute(); commit_ok, commit_err = connection:commit(); end if not(ok and commit_ok) then module:log("warn", "Failed to create index (%s), lookups may not be optimised", err or commit_err); end elseif params.driver == "MySQL" then -- COMPAT: Upgrade tables from 0.8.0 -- Failed to create, but check existing MySQL table here local stmt = connection:prepare("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); local ok = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then if stmt:rowcount() > 0 then module:log("info", "Upgrading database schema..."); local stmt = connection:prepare("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT"); local ok, err = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then module:log("info", "Database table automatically upgraded"); else module:log("error", "Failed to upgrade database schema (%s), please see " .."http://prosody.im/doc/mysql for help", err or "unknown error"); end end repeat until not stmt:fetch(); end end elseif params.driver ~= "SQLite3" then -- SQLite normally fails to prepare for existing table module:log("warn", "Prosody was not able to automatically check/create the database table (%s), " .."see http://prosody.im/doc/modules/mod_storage_sql#table_management for help.", err or "unknown error"); end end do -- process options to get a db connection local ok; prosody.unlock_globals(); ok, DBI = pcall(require, "DBI"); if not ok then package.loaded["DBI"] = {}; module:log("error", "Failed to load the LuaDBI library for accessing SQL databases: %s", DBI); module:log("error", "More information on installing LuaDBI can be found at http://prosody.im/doc/depends#luadbi"); end prosody.lock_globals(); if not ok or not DBI.Connect then return; -- Halt loading of this module end 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"); dburi = db2uri(params); connection = connections[dburi]; assert(connect()); -- 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 function dosql(sql, ...) if params.driver == "PostgreSQL" then sql = sql:gsub("`", "\""); end -- do prepared statement stuff local stmt, err = connection:prepare(sql); if not stmt and not test_connection() then error("connection failed"); end if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end -- run query local ok, err = stmt:execute(...); if not ok and not test_connection() then error("connection failed"); end if not ok then return nil, err; end return stmt; end local function getsql(sql, ...) return dosql(sql, host or "", user or "", store or "", ...); end local function setsql(sql, ...) local stmt, err = getsql(sql, ...); if not stmt then return stmt, err; end return stmt:affected(); end local function transact(...) -- ... end local function rollback(...) if connection then connection:rollback(); end -- FIXME check for rollback error? return ...; end local function commit(...) local success,err = connection:commit(); if not success then return nil, "SQL commit failed: "..tostring(err); end return ...; end local function keyval_store_get() local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?"); if not stmt then return rollback(nil, err); end local haveany; local result = {}; for row in stmt:rows(true) do haveany = true; local k = row.key; local v = deserialize(row.type, row.value); 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 return commit(haveany and result or nil); end local function keyval_store_set(data) local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=?"); if not affected then return rollback(affected, err); end 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); if not t then return rollback(t, value); end local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value); if not ok then return rollback(ok, err); end else extradata[key] = value; end end if next(extradata) ~= nil then local t, extradata = serialize(extradata); if not t then return rollback(t, extradata); end local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", "", t, extradata); if not ok then return rollback(ok, err); end end end return commit(true); end local keyval_store = {}; keyval_store.__index = keyval_store; function keyval_store:get(username) user,store = username,self.store; if not connection and not connect() then return nil, "Unable to connect to database"; end local success, ret, err = xpcall(keyval_store_get, debug.traceback); if not connection and connect() then success, ret, err = xpcall(keyval_store_get, debug.traceback); end if success then return ret, err; else return rollback(nil, ret); end end function keyval_store:set(username, data) user,store = username,self.store; if not connection and not connect() then return nil, "Unable to connect to database"; end local success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback); if not connection and connect() then success, ret, err = xpcall(function() return keyval_store_set(data); end, debug.traceback); end if success then return ret, err; else return rollback(nil, ret); end end function keyval_store:users() local stmt, err = dosql("SELECT DISTINCT `user` FROM `prosody` WHERE `host`=? AND `store`=?", host, self.store); if not stmt then return rollback(nil, err); end local next = stmt:rows(); return commit(function() local row = next(); return row and row[1]; end); end local function map_store_get(key) local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or ""); if not stmt then return rollback(nil, err); end local haveany; local result = {}; for row in stmt:rows(true) do haveany = true; local k = row.key; local v = deserialize(row.type, row.value); 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 return commit(haveany and result[key] or nil); end local function map_store_set(key, data) local affected, err = setsql("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or ""); if not affected then return rollback(affected, err); end if data and next(data) ~= nil then if type(key) == "string" and key ~= "" then local t, value = serialize(data); if not t then return rollback(t, value); end local ok, err = setsql("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", key, t, value); if not ok then return rollback(ok, err); end else -- TODO non-string keys end end return commit(true); end local map_store = {}; map_store.__index = map_store; function map_store:get(username, key) user,store = username,self.store; local success, ret, err = xpcall(function() return map_store_get(key); end, debug.traceback); if success then return ret, err; else return rollback(nil, ret); end end function map_store:set(username, key, data) user,store = username,self.store; local success, ret, err = xpcall(function() return map_store_set(key, data); end, debug.traceback); if success then return ret, err; else return rollback(nil, ret); end end local list_store = {}; list_store.__index = list_store; function list_store:scan(username, from, to, jid, typ) user,store = username,self.store; local cols = {"from", "to", "jid", "typ"}; local vals = { from , to , jid , typ }; local stmt, err; local query = "SELECT * FROM `prosodyarchive` WHERE `host`=? AND `user`=? AND `store`=?"; query = query.." ORDER BY time"; --local stmt, err = getsql("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", key or ""); return nil, "not-implemented" 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 local stmt, err = dosql(sql, host, username); if not stmt then return rollback(nil, err); end local next = stmt:rows(); return commit(function() local row = next(); return row and row[1]; end); end function driver:purge(username) local stmt, err = dosql("DELETE FROM `prosody` WHERE `host`=? AND `user`=?", host, username); if not stmt then return rollback(stmt, err); end local changed, err = stmt:affected(); if not changed then return rollback(changed, err); end return commit(true, changed); end module:provides("storage", driver); prosody-0.9.1/plugins/mod_auth_internal_hashed.lua0000644000175000017500000001136412213321667022330 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2010 Jeff Mitchell -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- 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) return ("%02x"):format(byte:byte()); end function to_hex(binary_string) return binary_string:gsub(".", replace_byte_with_hex); end end local from_hex; do local function replace_hex_with_byte(hex) return string.char(tonumber(hex, 16)); end function from_hex(hex_string) return hex_string:gsub("..", replace_hex_with_byte); end end -- Default; can be set per-user local iteration_count = 4096; local host = module.host; -- define auth provider local provider = {}; log("debug", "initializing internal_hashed authentication provider for host '%s'", host); function provider.test_password(username, password) local credentials = accounts:get(username) or {}; if credentials.password ~= nil and string.len(credentials.password) ~= 0 then if credentials.password ~= password then return nil, "Auth failed. Provided password is incorrect."; end if provider.set_password(username, credentials.password) == nil then return nil, "Auth failed. Could not set hashed password from plaintext."; else return true; end end if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then return nil, "Auth failed. Stored salt and iteration count information is not complete."; end local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); if valid and stored_key_hex == credentials.stored_key and server_key_hex == credentials.server_key then return true; else return nil, "Auth failed. Invalid username, password, or password hash information."; end end function provider.set_password(username, password) local account = accounts:get(username); if account then account.salt = account.salt or generate_uuid(); account.iteration_count = account.iteration_count or iteration_count; local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); account.stored_key = stored_key_hex account.server_key = server_key_hex account.password = nil; return accounts:set(username, account); end return nil, "Account not available."; end function provider.user_exists(username) 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"; end return true; end function provider.users() return accounts:users(); end function provider.create_user(username, password) if password == nil then 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 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 accounts:set(username, nil); end function provider.get_sasl_handler() local testpass_authentication_profile = { plain_test = function(sasl, username, password, realm) return usermanager.test_password(username, realm, password), true; end, scram_sha_1 = function(sasl, username, realm) local credentials = accounts:get(username); if not credentials then return; end if credentials.password then usermanager.set_password(username, credentials.password, host); credentials = accounts:get(username); if not credentials then return; end end local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt; stored_key = stored_key and from_hex(stored_key); server_key = server_key and from_hex(server_key); return stored_key, server_key, iteration_count, salt, true; end }; return new_sasl(host, testpass_authentication_profile); end module:provides("auth", provider); prosody-0.9.1/plugins/mod_http_errors.lua0000644000175000017500000000402412213321667020525 0ustar matthewmatthewmodule:set_global(); local server = require "net.http.server"; local codes = require "net.http.codes"; local show_private = module:get_option_boolean("http_errors_detailed", false); local always_serve = module:get_option_boolean("http_errors_always_show", true); local default_message = { module:get_option_string("http_errors_default_message", "That's all I know.") }; local default_messages = { [400] = { "What kind of request do you call that??" }; [403] = { "You're not allowed to do that." }; [404] = { "Whatever you were looking for is not here. %"; "Where did you put it?", "It's behind you.", "Keep looking." }; [500] = { "% Check your error log for more info."; "Gremlins.", "It broke.", "Don't look at me." }; }; local messages = setmetatable(module:get_option("http_errors_messages", {}), { __index = default_messages }); local html = [[

$title

$message

$extra

]]; html = html:gsub("%s%s+", ""); local entities = { ["<"] = "<", [">"] = ">", ["&"] = "&", ["'"] = "'", ["\""] = """, ["\n"] = "
", }; local function tohtml(plain) return (plain:gsub("[<>&'\"\n]", entities)); end local function get_page(code, extra) local message = messages[code]; if always_serve or message then message = message or default_message; return (html:gsub("$(%a+)", { title = rawget(codes, code) or ("Code "..tostring(code)); message = message[1]:gsub("%%", function () return message[math.random(2, math.max(#message,2))]; end); extra = tohtml(extra or ""); })); end end module:hook_object_event(server, "http-error", function (event) return get_page(event.code, (show_private and event.private_message) or event.message); end); prosody-0.9.1/plugins/mod_ping.lua0000644000175000017500000000161612213321667017113 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; module:add_feature("urn:xmpp:ping"); local function ping_handler(event) if event.stanza.attr.type == "get" then event.origin.send(st.reply(event.stanza)); return true; end end module:hook("iq/bare/urn:xmpp:ping:ping", ping_handler); module:hook("iq/host/urn:xmpp:ping:ping", ping_handler); -- Ad-hoc command local datetime = require "util.datetime".datetime; function ping_command_handler (self, data, state) local now = datetime(); return { info = "Pong\n"..now, status = "completed" }; end local adhoc_new = module:require "adhoc".new; local descriptor = adhoc_new("Ping", "ping", ping_command_handler); module:add_item ("adhoc", descriptor); prosody-0.9.1/plugins/mod_legacyauth.lua0000644000175000017500000000640312213321667020303 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local t_concat = table.concat; local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption") or not(module:get_option("allow_unencrypted_plain_auth")); local sessionmanager = require "core.sessionmanager"; local usermanager = require "core.usermanager"; local nodeprep = require "util.encodings".stringprep.nodeprep; local resourceprep = require "util.encodings".stringprep.resourceprep; module:add_feature("jabber:iq:auth"); module:hook("stream-features", function(event) local origin, features = event.origin, event.features; if secure_auth_only and not origin.secure then -- Sorry, not offering to insecure streams! return; elseif not origin.username then features:tag("auth", {xmlns='http://jabber.org/features/iq-auth'}):up(); end end); module:hook("stanza/iq/jabber:iq:auth:query", function(event) local session, stanza = event.origin, event.stanza; if session.type ~= "c2s_unauthed" then (session.sends2s or session.send)(st.error_reply(stanza, "cancel", "service-unavailable", "Legacy authentication is only allowed for unauthenticated client connections.")); return true; end if secure_auth_only and not session.secure then session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server")); return true; end local username = stanza.tags[1]:child_with_name("username"); local password = stanza.tags[1]:child_with_name("password"); local resource = stanza.tags[1]:child_with_name("resource"); if not (username and password and resource) then local reply = st.reply(stanza); session.send(reply:query("jabber:iq:auth") :tag("username"):up() :tag("password"):up() :tag("resource"):up()); else username, password, resource = t_concat(username), t_concat(password), t_concat(resource); username = nodeprep(username); resource = resourceprep(resource) if not (username and resource) then session.send(st.error_reply(stanza, "modify", "bad-request")); return true; end if usermanager.test_password(username, session.host, password) then -- Authentication successful! local success, err = sessionmanager.make_authenticated(session, username); if success then local err_type, err_msg; success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource); if not success then session.send(st.error_reply(stanza, err_type, err, err_msg)); session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager? return true; elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session.")); session:close(); -- FIXME undo resource bind and auth instead of closing the session? return true; end end session.send(st.reply(stanza)); else session.send(st.error_reply(stanza, "auth", "not-authorized")); end end return true; end); prosody-0.9.1/plugins/mod_admin_telnet.lua0000644000175000017500000010331512213321667020620 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local hostmanager = require "core.hostmanager"; local modulemanager = require "core.modulemanager"; local s2smanager = require "core.s2smanager"; local portmanager = require "core.portmanager"; local _G = _G; local prosody = _G.prosody; local hosts = prosody.hosts; local incoming_s2s = prosody.incoming_s2s; local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; local iterators = require "util.iterators"; local keys, values = iterators.keys, iterators.values; local jid = require "util.jid"; local jid_bare, jid_split = jid.bare, jid.split; local set, array = require "util.set", require "util.array"; local cert_verify_identity = require "util.x509".verify_identity; local envload = require "util.envload".envload; local envloadfile = require "util.envload".envloadfile; local commands = module:shared("commands") local def_env = module:shared("env"); local default_env_mt = { __index = def_env }; local core_post_stanza = prosody.core_post_stanza; local function redirect_output(_G, session) local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end }); env.dofile = function(name) local f, err = envloadfile(name, env); if not f then return f, err; end return f(); end; return env; end console = {}; function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; send = function (t) w(tostring(t)); end; print = function (...) local t = {}; for i=1,select("#", ...) do t[i] = tostring(select(i, ...)); end w("| "..table.concat(t, "\t").."\n"); end; disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); -- Load up environment with helper objects for name, t in pairs(def_env) do if type(t) == "table" then session.env[name] = setmetatable({ session = session }, { __index = t }); end end return session; end function console:process_line(session, line) local useglobalenv; if line:match("^>") then line = line:gsub("^>", ""); useglobalenv = true; elseif line == "\004" then commands["bye"](session, line); return; else local command = line:match("^%w+") or line:match("%p"); if commands[command] then commands[command](session, line); return; end end session.env._ = line; local chunkname = "=console"; local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil local chunk, err = envload("return "..line, chunkname, env); if not chunk then chunk, err = envload(line, chunkname, env); if not chunk then err = err:gsub("^%[string .-%]:%d+: ", ""); err = err:gsub("^:%d+: ", ""); err = err:gsub("''", "the end of the line"); session.print("Sorry, I couldn't understand that... "..err); return; end end local ranok, taskok, message = pcall(chunk); if not (ranok or message or useglobalenv) and commands[line:lower()] then commands[line:lower()](session, line); return; end if not ranok then session.print("Fatal error while running command, it did not complete"); session.print("Error: "..taskok); return; end if not message then session.print("Result: "..tostring(taskok)); return; elseif (not taskok) and message then session.print("Command completed with a problem"); session.print("Message: "..tostring(message)); return; end session.print("OK: "..tostring(message)); end local sessions = {}; function console_listener.onconnect(conn) -- Handle new connection local session = console:new_session(conn); sessions[conn] = session; printbanner(session); session.send(string.char(0)); end function console_listener.onincoming(conn, data) local session = sessions[conn]; local partial = session.partial_data; if partial then data = partial..data; end for line in data:gmatch("[^\n]*[\n\004]") do if session.closed then return end console:process_line(session, line); session.send(string.char(0)); end session.partial_data = data:match("[^\n]+$"); end function console_listener.ondisconnect(conn, err) local session = sessions[conn]; if session then session.disconnect(); sessions[conn] = nil; end end -- Console commands -- -- These are simple commands, not valid standalone in Lua function commands.bye(session) session.print("See you! :)"); session.closed = true; session.disconnect(); end commands.quit, commands.exit = commands.bye, commands.bye; commands["!"] = function (session, data) if data:match("^!!") and session.env._ then session.print("!> "..session.env._); return console_listener.onincoming(session.conn, session.env._); end local old, new = data:match("^!(.-[^\\])!(.-)!$"); if old and new then local ok, res = pcall(string.gsub, session.env._, old, new); if not ok then session.print(res) return; end session.print("!> "..res); return console_listener.onincoming(session.conn, res); end session.print("Sorry, not sure what you want"); end function commands.help(session, data) local print = session.print; local section = data:match("^help (%w+)"); if not section then print [[Commands are divided into multiple sections. For help on a particular section, ]] print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] print [[]] print [[c2s - Commands to manage local client-to-server sessions]] print [[s2s - Commands to manage sessions between this server and others]] print [[module - Commands to load/reload/unload modules/plugins]] print [[host - Commands to activate, deactivate and list virtual hosts]] print [[user - Commands to create and delete users, and change their passwords]] print [[server - Uptime, version, shutting down, etc.]] print [[port - Commands to manage ports the server is listening on]] print [[dns - Commands to manage and inspect the internal DNS resolver]] print [[config - Reloading the configuration, etc.]] print [[console - Help regarding the console itself]] elseif section == "c2s" then 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: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: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 print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] print [[module:unload(module, host) - The same, but just unloads the module from memory]] print [[module:list(host) - List the modules loaded on the specified host]] elseif section == "host" then print [[host:activate(hostname) - Activates the specified host]] print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] print [[host:list() - List the currently-activated hosts]] elseif section == "user" then print [[user:create(jid, password) - Create the specified user account]] print [[user:password(jid, password) - Set the password for the specified user account]] print [[user:delete(jid) - Permanently remove the specified user account]] print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] elseif section == "server" then print [[server:version() - Show the server's version number]] print [[server:uptime() - Show how long the server has been running]] print [[server:memory() - Show details about the server's memory usage]] print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] elseif section == "port" then print [[port:list() - Lists all network ports prosody currently listens on]] print [[port:close(port, interface) - Close a port]] elseif section == "dns" then print [[dns:lookup(name, type, class) - Do a DNS lookup]] print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]] print [[dns:purge() - Clear the DNS cache]] print [[dns:cache() - Show cached records]] elseif section == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] elseif section == "console" then print [[Hey! Welcome to Prosody's admin console.]] print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] print [[so you may have trouble using the arrow keys, etc. depending on your system.]] print [[]] print [[For now we offer a couple of handy shortcuts:]] print [[!! - Repeat the last command]] print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] print [[]] print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] print [[you can prefix a command with > to escape the console sandbox, and access everything in]] print [[the running server. Great fun, but be careful not to break anything :)]] end print [[]] end -- Session environment -- -- Anything in def_env will be accessible within the session as a global variable def_env.server = {}; function def_env.server:insane_reload() prosody.unlock_globals(); dofile "prosody" prosody = _G.prosody; return true, "Server reloaded"; end function def_env.server:version() return true, tostring(prosody.version or "unknown"); end function def_env.server:uptime() local t = os.time()-prosody.start_time; local seconds = t%60; t = (t - seconds)/60; local minutes = t%60; t = (t - minutes)/60; local hours = t%24; t = (t - hours)/24; local days = t; return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); end function def_env.server:shutdown(reason) prosody.shutdown(reason); return true, "Shutdown initiated"; end local function human(kb) local unit = "K"; if kb > 1024 then kb, unit = kb/1024, "M"; end return ("%0.2f%sB"):format(kb, unit); end function def_env.server:memory() if not pposix.meminfo then return true, "Lua is using "..collectgarbage("count"); end local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); local print = self.session.print; print("Process: "..human((mem.allocated+mem.allocated_mmap)/1024)); print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); return true, "OK"; end def_env.module = {}; local function get_hosts_set(hosts, module) if type(hosts) == "table" then if hosts[1] then return set.new(hosts); elseif hosts._items then return hosts; end elseif type(hosts) == "string" then return set.new { hosts }; elseif hosts == nil then local mm = require "modulemanager"; local hosts_set = set.new(array.collect(keys(prosody.hosts))) / function (host) return (prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module)) and host or nil; end; if module and mm.get_module("*", module) then hosts_set:add("*"); end return hosts_set; end end function def_env.module:load(name, hosts, config) local mm = require "modulemanager"; hosts = get_hosts_set(hosts); -- Load the module for each host local ok, err, count, mod = true, nil, 0, nil; for host in hosts do if (not mm.is_loaded(host, name)) then mod, err = mm.load(host, name, config); if not mod then ok = false; if err == "global-module-already-loaded" then if count > 0 then ok, err, count = true, nil, 1; end break; end self.session.print(err or "Unknown error loading module"); else count = count + 1; self.session.print("Loaded for "..mod.module.host); end end end return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); end function def_env.module:unload(name, hosts) local mm = require "modulemanager"; hosts = get_hosts_set(hosts, name); -- Unload the module for each host local ok, err, count = true, nil, 0; for host in hosts do if mm.is_loaded(host, name) then ok, err = mm.unload(host, name); if not ok then ok = false; self.session.print(err or "Unknown error unloading module"); else count = count + 1; self.session.print("Unloaded from "..host); end end end return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); end function def_env.module:reload(name, hosts) local mm = require "modulemanager"; hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b) if a == "*" then return true elseif b == "*" then return false else return a < b; end end); -- Reload the module for each host local ok, err, count = true, nil, 0; for _, host in ipairs(hosts) do if mm.is_loaded(host, name) then ok, err = mm.reload(host, name); if not ok then ok = false; self.session.print(err or "Unknown error reloading module"); else count = count + 1; if ok == nil then ok = true; end self.session.print("Reloaded on "..host); end end end return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); end function def_env.module:list(hosts) if hosts == nil then hosts = array.collect(keys(prosody.hosts)); table.insert(hosts, 1, "*"); end if type(hosts) == "string" then hosts = { hosts }; end if type(hosts) ~= "table" then return false, "Please supply a host or a list of hosts you would like to see"; end local print = self.session.print; for _, host in ipairs(hosts) do print((host == "*" and "Global" or host)..":"); local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort(); if #modules == 0 then if prosody.hosts[host] then print(" No modules loaded"); else print(" Host not found"); end else for _, name in ipairs(modules) do print(" "..name); end end end end def_env.config = {}; function def_env.config:load(filename, format) local config_load = require "core.configmanager".load; local ok, err = config_load(filename, format); if not ok then return false, err or "Unknown error loading config"; end return true, "Config loaded"; end function def_env.config:get(host, section, key) local config_get = require "core.configmanager".get return true, tostring(config_get(host, section, key)); end function def_env.config:reload() local ok, err = prosody.reload_config(); 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); end return true, "Done"; end function def_env.hosts:add(name) end def_env.c2s = {}; local function show_c2s(callback) for hostname, host in pairs(hosts) do for username, user in pairs(host.sessions or {}) do for resource, session in pairs(user.sessions or {}) do local jid = username.."@"..hostname.."/"..resource; callback(jid, session); end end end end function def_env.c2s:count(match_jid) local count = 0; show_c2s(function (jid, session) if (not match_jid) or jid:match(match_jid) then count = count + 1; end end); return true, "Total: "..count.." clients"; end function def_env.c2s:show(match_jid) local print, count = self.session.print, 0; local curr_host; show_c2s(function (jid, session) if curr_host ~= session.host then curr_host = session.host; print(curr_host); 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:child_with_name("show"); if status then status = status:get_text() or "[invalid!]"; else status = "available"; end end print(" "..jid.." - "..status.."("..priority..")"); end end); return true, "Total: "..count.." clients"; end function def_env.c2s:show_insecure(match_jid) local print, count = self.session.print, 0; show_c2s(function (jid, session) if ((not match_jid) or jid:match(match_jid)) and not session.secure then count = count + 1; print(jid); end end); return true, "Total: "..count.." insecure client connections"; end function def_env.c2s:show_secure(match_jid) local print, count = self.session.print, 0; show_c2s(function (jid, session) if ((not match_jid) or jid:match(match_jid)) and session.secure then count = count + 1; print(jid); end end); return true, "Total: "..count.." secure client connections"; end function def_env.c2s:close(match_jid) local count = 0; show_c2s(function (jid, session) if jid == match_jid or jid_bare(jid) == match_jid then count = count + 1; session:close(); end end); return true, "Total: "..count.." sessions closed"; end local function session_flags(session, line) if session.cert_identity_status == "valid" then line[#line+1] = "(secure)"; elseif session.secure then line[#line+1] = "(encrypted)"; end if session.compressed then line[#line+1] = "(compressed)"; end if session.smacks then line[#line+1] = "(sm)"; end if session.conn and session.conn:ip():match(":") then line[#line+1] = "(IPv6)"; end return table.concat(line, " "); end def_env.s2s = {}; function def_env.s2s:show(match_jid) local _print = self.session.print; local print = self.session.print; local count_in, count_out = 0,0; for host, host_session in pairs(hosts) do print = function (...) _print(host); _print(...); print = _print; end for remotehost, session in pairs(host_session.s2sout) do if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then count_out = count_out + 1; print(session_flags(session, {" ", host, "->", remotehost})); if session.sendq then print(" There are "..#session.sendq.." queued outgoing stanzas for this connection"); end if session.type == "s2sout_unauthed" then if session.connecting then print(" Connection not yet established"); if not session.srv_hosts then if not session.conn then print(" We do not yet have a DNS answer for this host's SRV records"); else print(" This host has no SRV records, using A record instead"); end elseif session.srv_choice then print(" We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts); local srv_choice = session.srv_hosts[session.srv_choice]; print(" Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269)); end elseif session.notopen then print(" The has not yet been opened"); elseif not session.dialback_key then print(" Dialback has not been initiated yet"); elseif session.dialback_key then print(" Dialback has been requested, but no result received"); end end end end local subhost_filter = function (h) return (match_jid and h:match(match_jid)); end for session in pairs(incoming_s2s) do if session.to_host == host and ((not match_jid) or host:match(match_jid) or (session.from_host and session.from_host:match(match_jid)) -- Pft! is what I say to list comprehensions or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then count_in = count_in + 1; print(session_flags(session, {" ", host, "<-", session.from_host or "(unknown)"})); if session.type == "s2sin_unauthed" then print(" Connection not yet authenticated"); end for name in pairs(session.hosts) do if name ~= session.from_host then print(" also hosts "..tostring(name)); end end end end print = _print; end for session in pairs(incoming_s2s) do if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then count_in = count_in + 1; print("Other incoming s2s connections"); print(" (unknown) <- "..(session.from_host or "(unknown)")); end end return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections"; end local function print_subject(print, subject) for _, entry in ipairs(subject) do print( (" %s: %q"):format( entry.name or entry.oid, entry.value:gsub("[\r\n%z%c]", " ") ) ); end end -- As much as it pains me to use the 0-based depths that OpenSSL does, -- I think there's going to be more confusion among operators if we -- break from that. local function print_errors(print, errors) for depth, t in pairs(errors) do print( (" %d: %s"):format( depth-1, table.concat(t, "\n| ") ) ); end end function def_env.s2s:showcert(domain) local ser = require "util.serialization".serialize; local print = self.session.print; local domain_sessions = set.new(array.collect(keys(incoming_s2s))) /function(session) return session.from_host == domain and session or nil; end; for local_host in values(prosody.hosts) do local s2sout = local_host.s2sout; if s2sout and s2sout[domain] then domain_sessions:add(s2sout[domain]); end end local cert_set = {}; for session in domain_sessions do local conn = session.conn; conn = conn and conn:socket(); if not conn.getpeerchain then if conn.dohandshake then error("This version of LuaSec does not support certificate viewing"); end else local cert = conn:getpeercertificate(); if cert then local certs = conn:getpeerchain(); local digest = cert:digest("sha1"); if not cert_set[digest] then local chain_valid, chain_errors = conn:getpeerverification(); cert_set[digest] = { { from = session.from_host, to = session.to_host, direction = session.direction }; chain_valid = chain_valid; chain_errors = chain_errors; certs = certs; }; else table.insert(cert_set[digest], { from = session.from_host, to = session.to_host, direction = session.direction }); end end end end local domain_certs = array.collect(values(cert_set)); -- Phew. We now have a array of unique certificates presented by domain. local n_certs = #domain_certs; if n_certs == 0 then return "No certificates found for "..domain; end local function _capitalize_and_colon(byte) return string.upper(byte)..":"; end local function pretty_fingerprint(hash) return hash:gsub("..", _capitalize_and_colon):sub(1, -2); end for cert_info in values(domain_certs) do local certs = cert_info.certs; local cert = certs[1]; print("---") print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); print(""); local n_streams = #cert_info; print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); for _, stream in ipairs(cert_info) do if stream.direction == "incoming" then print(" "..stream.to.." <- "..stream.from); else print(" "..stream.from.." -> "..stream.to); end end print(""); local chain_valid, errors = cert_info.chain_valid, cert_info.chain_errors; local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); if chain_valid then print("Trusted certificate: Yes"); else print("Trusted certificate: No"); print_errors(print, errors); end print(""); print("Issuer: "); print_subject(print, cert:issuer()); print(""); print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); print("Subject:"); print_subject(print, cert:subject()); end print("---"); return ("Showing "..n_certs.." certificate" ..(n_certs==1 and "" or "s") .." presented by "..domain.."."); end function def_env.s2s:close(from, to) local print, count = self.session.print, 0; if not (from and to) then return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'"; elseif from == to then return false, "Both from and to are the same... you can't do that :)"; end if hosts[from] and not hosts[to] then -- Is an outgoing connection local session = hosts[from].s2sout[to]; if not session then print("No outgoing connection from "..from.." to "..to) else (session.close or s2smanager.destroy_session)(session); count = count + 1; print("Closed outgoing session from "..from.." to "..to); end elseif hosts[to] and not hosts[from] then -- Is an incoming connection for session in pairs(incoming_s2s) do if session.to_host == to and session.from_host == from then (session.close or s2smanager.destroy_session)(session); count = count + 1; end end if count == 0 then print("No incoming connections from "..from.." to "..to); else print("Closed "..count.." incoming session"..((count == 1 and "") or "s").." from "..from.." to "..to); end elseif hosts[to] and hosts[from] then return false, "Both of the hostnames you specified are local, there are no s2s sessions to close"; else return false, "Neither of the hostnames you specified are being used on this server"; end return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end function def_env.s2s:closeall(host) local count = 0; if not host or type(host) ~= "string" then return false, "wrong syntax: please use s2s:closeall('hostname.tld')"; end if hosts[host] then for session in pairs(incoming_s2s) do if session.to_host == host then (session.close or s2smanager.destroy_session)(session); count = count + 1; end end for _, session in pairs(hosts[host].s2sout) do (session.close or s2smanager.destroy_session)(session); count = count + 1; end else for session in pairs(incoming_s2s) do if session.from_host == host then (session.close or s2smanager.destroy_session)(session); count = count + 1; end end for _, h in pairs(hosts) do if h.s2sout[host] then (h.s2sout[host].close or s2smanager.destroy_session)(h.s2sout[host]); count = count + 1; end end end if count == 0 then return false, "No sessions to close."; else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end end def_env.host = {}; def_env.hosts = def_env.host; function def_env.host:activate(hostname, config) return hostmanager.activate(hostname, config); end function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end function def_env.host:list() local print = self.session.print; local i = 0; for host in values(array.collect(keys(prosody.hosts)):sort()) do i = i + 1; print(host); end return true, i.." hosts"; end def_env.port = {}; function def_env.port:list() local print = self.session.print; local services = portmanager.get_active_services().data; local ordered_services, n_ports = {}, 0; for service, interfaces in pairs(services) do table.insert(ordered_services, service); end table.sort(ordered_services); for _, service in ipairs(ordered_services) do local ports_list = {}; for interface, ports in pairs(services[service]) do for port in pairs(ports) do table.insert(ports_list, "["..interface.."]:"..port); end end n_ports = n_ports + #ports_list; print(service..": "..table.concat(ports_list, ", ")); end return true, #ordered_services.." services listening on "..n_ports.." ports"; end function def_env.port:close(close_port, close_interface) close_port = assert(tonumber(close_port), "Invalid port number"); local n_closed = 0; local services = portmanager.get_active_services().data; for service, interfaces in pairs(services) do for interface, ports in pairs(interfaces) do if not close_interface or close_interface == interface then if ports[close_port] then self.session.print("Closing ["..interface.."]:"..close_port.."..."); local ok, err = portmanager.close(interface, close_port) if not ok then self.session.print("Failed to close "..interface.." "..close_port..": "..err); else n_closed = n_closed + 1; end end end end end return true, "Closed "..n_closed.." ports"; end def_env.muc = {}; local console_room_mt = { __index = function (self, k) return self.room[k]; end; __tostring = function (self) return "MUC room <"..self.room.jid..">"; end; }; 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); if not room then return nil, host end if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end 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; end return setmetatable({ room = room_obj }, console_room_mt); end local um = require"core.usermanager"; def_env.user = {}; function def_env.user:create(jid, password) local username, host = jid_split(jid); if not hosts[host] then return nil, "No such host: "..host; elseif um.user_exists(username, host) then return nil, "User exists"; end local ok, err = um.create_user(username, password, host); if ok then return true, "User created"; else return nil, "Could not create user: "..err; end end function def_env.user:delete(jid) local username, host = jid_split(jid); if not hosts[host] then return nil, "No such host: "..host; elseif not um.user_exists(username, host) then return nil, "No such user"; end local ok, err = um.delete_user(username, host); if ok then return true, "User deleted"; else return nil, "Could not delete user: "..err; end end function def_env.user:password(jid, password) local username, host = jid_split(jid); if not hosts[host] then return nil, "No such host: "..host; elseif not um.user_exists(username, host) then return nil, "No such user"; end local ok, err = um.set_password(username, password, host); if ok then return true, "User password changed"; else return nil, "Could not change password for user: "..err; end end function def_env.user:list(host, pat) if not host then return nil, "No host given"; elseif not hosts[host] then return nil, "No such host"; end local print = self.session.print; local total, matches = 0, 0; for user in um.users(host) do if not pat or user:match(pat) then print(user.."@"..host); matches = matches + 1; end total = total + 1; end return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; end def_env.xmpp = {}; local st = require "util.stanza"; function def_env.xmpp:ping(localhost, remotehost) if hosts[localhost] then core_post_stanza(hosts[localhost], st.iq{ from=localhost, to=remotehost, type="get", id="ping" } :tag("ping", {xmlns="urn:xmpp:ping"})); return true, "Sent ping"; else return nil, "No such host"; end end def_env.dns = {}; local adns = require"net.adns"; local dns = require"net.dns"; function def_env.dns:lookup(name, typ, class) local ret = "Query sent"; local print = self.session.print; local function handler(...) ret = "Got response"; print(...); end adns.lookup(handler, name, typ, class); return true, ret; end function def_env.dns:addnameserver(...) dns.addnameserver(...) return true end function def_env.dns:setnameserver(...) dns.setnameserver(...) return true end function def_env.dns:purge() dns.purge() return true end function def_env.dns:cache() return true, "Cache:\n"..tostring(dns.cache()) end ------------- function printbanner(session) local option = module:get_option("console_banner"); if option == nil or option == "full" or option == "graphic" then session.print [[ ____ \ / _ | _ \ _ __ ___ ___ _-_ __| |_ _ | |_) | '__/ _ \/ __|/ _ \ / _` | | | | | __/| | | (_) \__ \ |_| | (_| | |_| | |_| |_| \___/|___/\___/ \__,_|\__, | A study in simplicity |___/ ]] end if option == nil or option == "short" or option == "full" then session.print("Welcome to the Prosody administration console. For a list of commands, type: help"); session.print("You may find more help on using this console in our online documentation at "); session.print("http://prosody.im/doc/console\n"); end if option and option ~= "short" and option ~= "full" and option ~= "graphic" then if type(option) == "string" then session.print(option) elseif type(option) == "function" then module:log("warn", "Using functions as value for the console_banner option is no longer supported"); end end end module:provides("net", { name = "console"; listener = console_listener; default_port = 5582; private = true; }); prosody-0.9.1/plugins/mod_presence.lua0000644000175000017500000003424612213321667017767 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = module._log; local require = require; local pairs = pairs; local t_concat, t_insert = table.concat, table.insert; local s_find = string.find; local tonumber = tonumber; local core_post_stanza = prosody.core_post_stanza; local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local datetime = require "util.datetime"; local hosts = prosody.hosts; local bare_sessions = prosody.bare_sessions; local full_sessions = prosody.full_sessions; local NULL = {}; local rostermanager = require "core.rostermanager"; local sessionmanager = require "core.sessionmanager"; local function select_top_resources(user) local priority = 0; local recipients = {}; for _, session in pairs(user.sessions) do -- find resource with greatest priority if session.presence then -- TODO check active privacy list for session local p = session.priority; if p > priority then priority = p; recipients = {session}; elseif p == priority then t_insert(recipients, session); end end end return recipients; end local function recalc_resource_map(user) if user then user.top_resources = select_top_resources(user); if #user.top_resources == 0 then user.top_resources = nil; end end end local ignore_presence_priority = module:get_option("ignore_presence_priority"); function handle_normal_presence(origin, stanza) if ignore_presence_priority then local priority = stanza:child_with_name("priority"); if priority and priority[1] ~= "0" then for i=#priority.tags,1,-1 do priority.tags[i] = nil; end for i=#priority,1,-1 do priority[i] = nil; end priority[1] = "0"; end end local priority = stanza:child_with_name("priority"); if priority and #priority > 0 then priority = t_concat(priority); if s_find(priority, "^[+-]?[0-9]+$") then priority = tonumber(priority); if priority < -128 then priority = -128 end if priority > 127 then priority = 127 end else priority = 0; end else priority = 0; end if full_sessions[origin.full_jid] then -- if user is still connected origin.send(stanza); -- reflect their presence back to them end local roster = origin.roster; local node, host = origin.username, origin.host; local user = bare_sessions[node.."@"..host]; for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources if res ~= origin and res.presence then -- to resource stanza.attr.to = res.full_jid; core_post_stanza(origin, stanza, true); end end for jid, item in pairs(roster) do -- broadcast to all interested contacts if item.subscription == "both" or item.subscription == "from" then stanza.attr.to = jid; core_post_stanza(origin, stanza, true); end end if stanza.attr.type == nil and not origin.presence then -- initial presence origin.presence = stanza; -- FIXME repeated later local probe = st.presence({from = origin.full_jid, type = "probe"}); for jid, item in pairs(roster) do -- probe all contacts we are subscribed to if item.subscription == "both" or item.subscription == "to" then probe.attr.to = jid; core_post_stanza(origin, probe, true); end end for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources if res ~= origin and res.presence then res.presence.attr.to = origin.full_jid; core_post_stanza(res, res.presence, true); res.presence.attr.to = nil; end end if roster.pending then -- resend incoming subscription requests for jid in pairs(roster.pending) do origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original? end end local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host}); for jid, item in pairs(roster) do -- resend outgoing subscription requests if item.ask then request.attr.to = jid; core_post_stanza(origin, request, true); end end if priority >= 0 then local event = { origin = origin } module:fire_event('message/offline/broadcast', event); end end if stanza.attr.type == "unavailable" then origin.presence = nil; if origin.priority then origin.priority = nil; recalc_resource_map(user); end if origin.directed then for jid in pairs(origin.directed) do stanza.attr.to = jid; core_post_stanza(origin, stanza, true); end origin.directed = nil; end else origin.presence = stanza; stanza:tag("delay", { xmlns = "urn:xmpp:delay", from = host, stamp = datetime.datetime() }):up(); if origin.priority ~= priority then origin.priority = priority; recalc_resource_map(user); end end stanza.attr.to = nil; -- reset it end function send_presence_of_available_resources(user, host, jid, recipient_session, stanza) local h = hosts[host]; local count = 0; if h and h.type == "local" then local u = h.sessions[user]; if u then for k, session in pairs(u.sessions) do local pres = session.presence; if pres then if stanza then pres = stanza; pres.attr.from = session.full_jid; end pres.attr.to = jid; core_post_stanza(session, pres, true); pres.attr.to = nil; count = count + 1; end end end end log("debug", "broadcasted presence of %d resources from %s@%s to %s", count, user, host, jid); return count; end function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) local node, host = jid_split(from_bare); if to_bare == from_bare then return; end -- No self contacts local st_from, st_to = stanza.attr.from, stanza.attr.to; stanza.attr.from, stanza.attr.to = from_bare, to_bare; log("debug", "outbound presence %s from %s for %s", stanza.attr.type, from_bare, to_bare); if stanza.attr.type == "probe" then stanza.attr.from, stanza.attr.to = st_from, st_to; return; elseif stanza.attr.type == "subscribe" then -- 1. route stanza -- 2. roster push (subscription = none, ask = subscribe) if rostermanager.set_contact_pending_out(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); end -- else file error core_post_stanza(origin, stanza); elseif stanza.attr.type == "unsubscribe" then -- 1. route stanza -- 2. roster push (subscription = none or from) if rostermanager.unsubscribe(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed? end -- else file error core_post_stanza(origin, stanza); elseif stanza.attr.type == "subscribed" then -- 1. route stanza -- 2. roster_push () -- 3. send_presence_of_available_resources if rostermanager.subscribed(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); end core_post_stanza(origin, stanza); send_presence_of_available_resources(node, host, to_bare, origin); elseif stanza.attr.type == "unsubscribed" then -- 1. send unavailable -- 2. route stanza -- 3. roster push (subscription = from or both) local success, pending_in, subscribed = rostermanager.unsubscribed(node, host, to_bare); if success then if subscribed then rostermanager.roster_push(node, host, to_bare); end core_post_stanza(origin, stanza); if subscribed then send_presence_of_available_resources(node, host, to_bare, origin, st.presence({ type = "unavailable" })); end end else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end stanza.attr.from, stanza.attr.to = st_from, st_to; return true; end function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare) local node, host = jid_split(to_bare); local st_from, st_to = stanza.attr.from, stanza.attr.to; stanza.attr.from, stanza.attr.to = from_bare, to_bare; log("debug", "inbound presence %s from %s for %s", stanza.attr.type, from_bare, to_bare); if stanza.attr.type == "probe" then local result, err = rostermanager.is_contact_subscribed(node, host, from_bare); if result then if 0 == send_presence_of_available_resources(node, host, st_from, origin) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity end elseif not err then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true); end elseif stanza.attr.type == "subscribe" then if rostermanager.is_contact_subscribed(node, host, from_bare) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); -- already subscribed -- Sending presence is not clearly stated in the RFC, but it seems appropriate if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- TODO send last activity end else core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt if not rostermanager.is_contact_pending_in(node, host, from_bare) then if rostermanager.set_contact_pending_in(node, host, from_bare) then sessionmanager.send_to_available_resources(node, host, stanza); end -- TODO else return error, unable to save end end elseif stanza.attr.type == "unsubscribe" then if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end elseif stanza.attr.type == "subscribed" then if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end elseif stanza.attr.type == "unsubscribed" then if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then sessionmanager.send_to_interested_resources(node, host, stanza); rostermanager.roster_push(node, host, from_bare); end else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end stanza.attr.from, stanza.attr.to = st_from, st_to; return true; end local outbound_presence_handler = function(data) -- outbound presence recieved local origin, stanza = data.origin, data.stanza; local to = stanza.attr.to; if to then local t = stanza.attr.type; if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes return handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local to_bare = jid_bare(to); local roster = origin.roster; if roster and not(roster[to_bare] and (roster[to_bare].subscription == "both" or roster[to_bare].subscription == "from")) then -- directed presence origin.directed = origin.directed or {}; if t then -- removing from directed presence list on sending an error or unavailable origin.directed[to] = nil; -- FIXME does it make more sense to add to_bare rather than to? else origin.directed[to] = true; -- FIXME does it make more sense to add to_bare rather than to? end end end -- TODO maybe handle normal presence here, instead of letting it pass to incoming handlers? end module:hook("pre-presence/full", outbound_presence_handler); module:hook("pre-presence/bare", outbound_presence_handler); module:hook("pre-presence/host", outbound_presence_handler); module:hook("presence/bare", function(data) -- inbound presence to bare JID recieved local origin, stanza = data.origin, data.stanza; local to = stanza.attr.to; local t = stanza.attr.type; if to then if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to bare JID return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local user = bare_sessions[to]; if user then for _, session in pairs(user.sessions) do if session.presence then -- only send to available resources session.send(stanza); end end end -- no resources not online, discard elseif not t or t == "unavailable" then handle_normal_presence(origin, stanza); else origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type")); end return true; end); module:hook("presence/full", function(data) -- inbound presence to full JID recieved local origin, stanza = data.origin, data.stanza; local t = stanza.attr.type; if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to full JID return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to)); end local session = full_sessions[stanza.attr.to]; if session then -- TODO fire post processing event session.send(stanza); end -- resource not online, discard return true; end); module:hook("presence/host", function(data) -- inbound presence to the host local stanza = data.stanza; local from_bare = jid_bare(stanza.attr.from); local t = stanza.attr.type; if t == "probe" then core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id })); elseif t == "subscribe" then core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" })); core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id })); end return true; end); module:hook("resource-unbind", function(event) local session, err = event.session, event.error; -- Send unavailable presence if session.presence then local pres = st.presence{ type = "unavailable" }; if err then pres:tag("status"):text("Disconnected: "..err):up(); end session:dispatch_stanza(pres); elseif session.directed then local pres = st.presence{ type = "unavailable", from = session.full_jid }; if err then pres:tag("status"):text("Disconnected: "..err):up(); end for jid in pairs(session.directed) do pres.attr.to = jid; core_post_stanza(session, pres, true); end session.directed = nil; end end); prosody-0.9.1/plugins/mod_register.lua0000644000175000017500000002350212213321667020000 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; 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; 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 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 }; nick = { name = "nick", type = "text-single", label = "Nickname" }; name = { name = "name", type = "text-single", label = "Full Name" }; first = { name = "first", type = "text-single", label = "Given Name" }; last = { name = "last", type = "text-single", label = "Family Name" }; email = { name = "email", type = "text-single", label = "Email" }; address = { name = "address", type = "text-single", label = "Street" }; city = { name = "city", type = "text-single", label = "City" }; state = { name = "state", type = "text-single", label = "State" }; zip = { name = "zip", type = "text-single", label = "Postal code" }; phone = { name = "phone", type = "text-single", label = "Telephone number" }; url = { name = "url", type = "text-single", label = "Webpage" }; date = { name = "date", type = "text-single", label = "Birth date" }; }; local registration_form = dataform_new{ title = "Creating a new account"; instructions = "Choose a username and password for use with this service."; field_map.username; field_map.password; }; local registration_query = st.stanza("query", {xmlns = "jabber:iq:register"}) :tag("instructions"):text("Choose a username and password for use with this service."):up() :tag("username"):up() :tag("password"):up(); for _, field in ipairs(additional_fields) do if type(field) == "table" then registration_form[#registration_form + 1] = field; else if field:match("%+$") then field = field:sub(1, #field - 1); field_map[field].required = true; end registration_form[#registration_form + 1] = field_map[field]; registration_query:tag(field):up(); end end registration_query:add_child(registration_form:form()); module:add_feature("jabber:iq:register"); local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up(); module:hook("stream-features", function(event) local session, features = event.origin, event.features; -- Advertise registration to unauthorized clients only. if not(allow_registration) or session.type ~= "c2s_unauthed" then return end features:add_child(register_stream_feature); end); local function handle_registration_stanza(event) local session, stanza = event.origin, event.stanza; local query = stanza.tags[1]; if stanza.attr.type == "get" then local reply = st.reply(stanza); reply:tag("query", {xmlns = "jabber:iq:register"}) :tag("registered"):up() :tag("username"):text(session.username):up() :tag("password"):up(); session.send(reply); else -- stanza.attr.type == "set" if query.tags[1] and query.tags[1].name == "remove" then local username, host = session.username, session.host; local old_session_close = session.close; session.close = function(session, ...) session.send(st.reply(stanza)); return old_session_close(session, ...); end 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); 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); module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session }); else local username = nodeprep(query:get_child("username"):get_text()); local password = query:get_child("password"):get_text(); if username and password then if username == session.username then if usermanager_set_password(username, password, session.host) then session.send(st.reply(stanza)); else -- TODO unable to write file, file may be locked, etc, what's the correct error? session.send(st.error_reply(stanza, "wait", "internal-server-error")); end else session.send(st.error_reply(stanza, "modify", "bad-request")); end else session.send(st.error_reply(stanza, "modify", "bad-request")); end end end return true; end module:hook("iq/self/jabber:iq:register:query", handle_registration_stanza); if compat then module:hook("iq/host/jabber:iq:register:query", function (event) local session, stanza = event.origin, event.stanza; if session.type == "c2s" and jid_bare(stanza.attr.to) == session.host then return handle_registration_stanza(event); end end); end local function parse_response(query) local form = query:get_child("x", "jabber:x:data"); if form then return registration_form:data(form); else local data = {}; local errors = {}; for _, field in ipairs(registration_form) do local name, required = field.name, field.required; if field_map[name] then data[name] = query:get_child_text(name); if (not data[name] or #data[name] == 0) and required then errors[name] = "Required value missing"; end end end if next(errors) then return data, errors; end return data; end end local recent_ips = {}; local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations"); local whitelist_only = module:get_option("whitelist_registration_only"); local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" }; local blacklisted_ips = module:get_option("registration_blacklist") or {}; for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end module:hook("stanza/iq/jabber:iq:register:query", function(event) local session, stanza = event.origin, event.stanza; if not(allow_registration) or session.type ~= "c2s_unauthed" then session.send(st.error_reply(stanza, "cancel", "service-unavailable")); else local query = stanza.tags[1]; if stanza.attr.type == "get" then local reply = st.reply(stanza); reply:add_child(registration_query); session.send(reply); elseif stanza.attr.type == "set" then if query.tags[1] and query.tags[1].name == "remove" then session.send(st.error_reply(stanza, "auth", "registration-required")); else local data, errors = parse_response(query); if errors then session.send(st.error_reply(stanza, "modify", "not-acceptable")); 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"); 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(); end end local username, password = nodeprep(data.username), data.password; data.username, data.password = nil, nil; local host = module.host; if not username or username == "" then session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid.")); return true; end local user = { username = username , host = host, allowed = true } module:fire_event("user-registering", user); if not user.allowed then session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is forbidden.")); elseif usermanager_user_exists(username, host) then session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); else -- 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 account_details:set(username, data) then usermanager_delete_user(username, host); session.send(error_reply); return true; end session.send(st.reply(stanza)); -- user created! module:log("info", "User account created: %s@%s", username, host); module:fire_event("user-registered", { username = username, host = host, source = "mod_register", session = session }); else session.send(error_reply); end end end end end end return true; end); prosody-0.9.1/plugins/mod_http_files.lua0000644000175000017500000001072712213321667020322 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:depends("http"); local server = require"net.http.server"; local lfs = require "lfs"; local os_date = os.date; local open = io.open; local stat = lfs.attributes; local build_path = require"socket.url".build_path; local base_path = module:get_option_string("http_files_dir", module:get_option_string("http_path")); local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" }); local directory_index = module:get_option_boolean("http_dir_listing"); local mime_map = module:shared("/*/http_files/mime").types; if not mime_map then mime_map = { html = "text/html", htm = "text/html", xml = "application/xml", txt = "text/plain", css = "text/css", js = "application/javascript", png = "image/png", gif = "image/gif", jpeg = "image/jpeg", jpg = "image/jpeg", svg = "image/svg+xml", }; module:shared("/*/http_files/mime").types = mime_map; local mime_types, err = open(module:get_option_string("mime_types_file", "/etc/mime.types"),"r"); if mime_types then local mime_data = mime_types:read("*a"); mime_types:close(); setmetatable(mime_map, { __index = function(t, ext) local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; t[ext] = typ; return typ; end }); end end local cache = setmetatable({}, { __mode = "kv" }); -- Let the garbage collector have it if it wants to. function serve(opts) if type(opts) ~= "table" then -- assume path string opts = { path = opts }; end local base_path = opts.path; local dir_indices = opts.index_files or dir_indices; local directory_index = opts.directory_index; local function serve_file(event, path) local request, response = event.request, event.response; local orig_path = request.path; local full_path = base_path .. (path and "/"..path or ""); local attr = stat(full_path); if not attr then return 404; end local request_headers, response_headers = request.headers, response.headers; local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); response_headers.last_modified = last_modified; local etag = ("%02x-%x-%x-%x"):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); response_headers.etag = etag; local if_none_match = request_headers.if_none_match local if_modified_since = request_headers.if_modified_since; if etag == if_none_match or (not if_none_match and last_modified == if_modified_since) then return 304; end local data = cache[orig_path]; if data and data.etag == etag then response_headers.content_type = data.content_type; data = data.data; elseif attr.mode == "directory" and path then if full_path:sub(-1) ~= "/" then local path = { is_absolute = true, is_directory = true }; for dir in orig_path:gmatch("[^/]+") do path[#path+1]=dir; end response_headers.location = build_path(path); return 301; end for i=1,#dir_indices do if stat(full_path..dir_indices[i], "mode") == "file" then return serve_file(event, path..dir_indices[i]); end end if directory_index then data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path }); end if not data then return 403; end cache[orig_path] = { data = data, content_type = mime_map.html; etag = etag; }; response_headers.content_type = mime_map.html; else local f, err = open(full_path, "rb"); if f then data, err = f:read("*a"); f:close(); end if not data then module:log("debug", "Could not open or read %s. Error was %s", full_path, err); return 403; end local ext = full_path:match("%.([^./]+)$"); local content_type = ext and mime_map[ext]; cache[orig_path] = { data = data; content_type = content_type; etag = etag }; response_headers.content_type = content_type; end return response:send(data); end return serve_file; end function wrap_route(routes) for route,handler in pairs(routes) do if type(handler) ~= "function" then routes[route] = serve(handler); end end return routes; end if base_path then module:provides("http", { route = { ["GET /*"] = serve { path = base_path; directory_index = directory_index; } }; }); else module:log("debug", "http_files_dir not set, assuming use by some other module"); end prosody-0.9.1/plugins/mod_private.lua0000644000175000017500000000273512213321667017633 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local private_storage = module:open_store(); module:add_feature("jabber:iq:private"); module:hook("iq/self/jabber:iq:private:query", function(event) local origin, stanza = event.origin, event.stanza; local type = stanza.attr.type; local query = stanza.tags[1]; if #query.tags == 1 then local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; local data, err = private_storage:get(origin.username); if err then origin.send(st.error_reply(stanza, "wait", "internal-server-error")); return true; end if stanza.attr.type == "get" then if data and data[key] then origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"}):add_child(st.deserialize(data[key]))); else origin.send(st.reply(stanza):add_child(stanza.tags[1])); end else -- set if not data then data = {}; end; if #tag == 0 then data[key] = nil; else data[key] = st.preserialize(tag); end -- TODO delete datastore if empty if private_storage:set(origin.username, data) then origin.send(st.reply(stanza)); else origin.send(st.error_reply(stanza, "wait", "internal-server-error")); end end else origin.send(st.error_reply(stanza, "modify", "bad-format")); end return true; end); prosody-0.9.1/plugins/mod_pubsub.lua0000644000175000017500000003165312213321667017462 0ustar matthewmatthewlocal pubsub = require "util.pubsub"; local st = require "util.stanza"; local jid_bare = require "util.jid".bare; local uuid_generate = require "util.uuid".generate; local usermanager = require "core.usermanager"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false); local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false); local pubsub_disco_name = module:get_option("name"); if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end local service; local handlers = {}; 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 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); return true; end end local pubsub_errors = { ["conflict"] = { "cancel", "conflict" }; ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" }; ["jid-required"] = { "modify", "bad-request", nil, "jid-required" }; ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" }; ["item-not-found"] = { "cancel", "item-not-found" }; ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" }; ["forbidden"] = { "cancel", "forbidden" }; }; function pubsub_error_reply(stanza, error) local e = pubsub_errors[error]; local reply = st.error_reply(stanza, unpack(e, 1, 3)); if e[4] then reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up(); end return reply; end function handlers.get_items(origin, stanza, items) local node = items.attr.node; local item = items:get_child("item"); local id = item and item.attr.id; if not node then return origin.send(pubsub_error_reply(stanza, "nodeid-required")); end local ok, results = service:get_items(node, stanza.attr.from, id); if not ok then return origin.send(pubsub_error_reply(stanza, results)); end local data = st.stanza("items", { node = node }); for _, entry in pairs(results) do data:add_child(entry); end local reply; if data then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :add_child(data); else reply = pubsub_error_reply(stanza, "item-not-found"); end return origin.send(reply); end function handlers.get_subscriptions(origin, stanza, subscriptions) local node = subscriptions.attr.node; local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from); if not ok then return origin.send(pubsub_error_reply(stanza, ret)); end local reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("subscriptions"); for _, sub in ipairs(ret) do reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up(); end return origin.send(reply); end function handlers.set_create(origin, stanza, create) local node = create.attr.node; local ok, ret, reply; if node then ok, ret = service:create(node, stanza.attr.from); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end else repeat node = uuid_generate(); ok, ret = service:create(node, stanza.attr.from); until ok or ret ~= "conflict"; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("create", { node = node }); else reply = pubsub_error_reply(stanza, ret); end end return origin.send(reply); end function handlers.set_delete(origin, stanza, delete) local node = delete.attr.node; local reply, notifier; if not node then return origin.send(pubsub_error_reply(stanza, "nodeid-required")); end local ok, ret = service:delete(node, stanza.attr.from); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end return origin.send(reply); end function handlers.set_subscribe(origin, stanza, subscribe) local node, jid = subscribe.attr.node, subscribe.attr.jid; if not (node and jid) then return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); end --[[ local options_tag, options = stanza.tags[1]:get_child("options"), nil; if options_tag then options = options_form:data(options_tag.tags[1]); end --]] local options_tag, options; -- FIXME local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options); local reply; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("subscription", { node = node, jid = jid, subscription = "subscribed" }):up(); if options_tag then reply:add_child(options_tag); end else reply = pubsub_error_reply(stanza, ret); end origin.send(reply); end function handlers.set_unsubscribe(origin, stanza, unsubscribe) local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid; if not (node and jid) then return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid")); end local ok, ret = service:remove_subscription(node, stanza.attr.from, jid); local reply; if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end return origin.send(reply); end function handlers.set_publish(origin, stanza, publish) local node = publish.attr.node; if not node then return origin.send(pubsub_error_reply(stanza, "nodeid-required")); end local item = publish:get_child("item"); local id = (item and item.attr.id); if not id then id = uuid_generate(); if item then item.attr.id = id; end end local ok, ret = service:publish(node, stanza.attr.from, id, item); local reply; if ok then reply = st.reply(stanza) :tag("pubsub", { xmlns = xmlns_pubsub }) :tag("publish", { node = node }) :tag("item", { id = id }); else reply = pubsub_error_reply(stanza, ret); end return origin.send(reply); end function handlers.set_retract(origin, stanza, retract) local node, notify = retract.attr.node, retract.attr.notify; notify = (notify == "1") or (notify == "true"); local item = retract:get_child("item"); local id = item and item.attr.id if not (node and id) then return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required")); end local reply, notifier; if notify then notifier = st.stanza("retract", { id = id }); end local ok, ret = service:retract(node, stanza.attr.from, id, notifier); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end return origin.send(reply); end function handlers.set_purge(origin, stanza, purge) local node, notify = purge.attr.node, purge.attr.notify; notify = (notify == "1") or (notify == "true"); local reply; if not node then return origin.send(pubsub_error_reply(stanza, "nodeid-required")); end local ok, ret = service:purge(node, stanza.attr.from, notify); if ok then reply = st.reply(stanza); else reply = pubsub_error_reply(stanza, ret); end return origin.send(reply); end function simple_broadcast(kind, node, jids, item) if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace end local message = st.message({ from = module.host, type = "headline" }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }) :add_child(item); for jid in pairs(jids) do module:log("debug", "Sending notification to %s", jid); message.attr.to = jid; module:send(message); end end module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); local disco_info; local feature_map = { create = { "create-nodes", "instant-nodes", "item-ids" }; retract = { "delete-items", "retract-items" }; purge = { "purge-nodes" }; publish = { "publish", autocreate_on_publish and "auto-create" }; delete = { "delete-nodes" }; get_items = { "retrieve-items" }; add_subscription = { "subscribe" }; get_subscriptions = { "retrieve-subscriptions" }; }; local function add_disco_features_from_service(disco, service) for method, features in pairs(feature_map) do if service[method] then for _, feature in ipairs(features) do if feature then disco:tag("feature", { var = xmlns_pubsub.."#"..feature }):up(); end end end end for affiliation in pairs(service.config.capabilities) do if affiliation ~= "none" and affiliation ~= "owner" then disco:tag("feature", { var = xmlns_pubsub.."#"..affiliation.."-affiliation" }):up(); end end end local function build_disco_info(service) local disco_info = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" }) :tag("identity", { category = "pubsub", type = "service", name = pubsub_disco_name }):up() :tag("feature", { var = "http://jabber.org/protocol/pubsub" }):up(); add_disco_features_from_service(disco_info, service); return disco_info; end module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function (event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; if not node then return origin.send(st.reply(stanza):add_child(disco_info)); else local ok, ret = service:get_nodes(stanza.attr.from); if ok and not ret[node] then ok, ret = false, "item-not-found"; end if not ok then return origin.send(pubsub_error_reply(stanza, ret)); end local reply = st.reply(stanza) :tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node }) :tag("identity", { category = "pubsub", type = "leaf" }); return origin.send(reply); end end); local function handle_disco_items_on_node(event) local stanza, origin = event.stanza, event.origin; local query = stanza.tags[1]; local node = query.attr.node; local ok, ret = service:get_items(node, stanza.attr.from); if not ok then return origin.send(pubsub_error_reply(stanza, ret)); end local reply = st.reply(stanza) :tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = node }); for id, item in pairs(ret) do reply:tag("item", { jid = module.host, name = id }):up(); end return origin.send(reply); end module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function (event) if event.stanza.tags[1].attr.node then return handle_disco_items_on_node(event); end local ok, ret = service:get_nodes(event.stanza.attr.from); if not ok then event.origin.send(pubsub_error_reply(event.stanza, ret)); else local reply = st.reply(event.stanza) :tag("query", { xmlns = "http://jabber.org/protocol/disco#items" }); for node, node_obj in pairs(ret) do reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up(); end event.origin.send(reply); end return true; end); local admin_aff = module:get_option_string("default_admin_affiliation", "owner"); local function get_affiliation(jid) local bare_jid = jid_bare(jid); if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then return admin_aff; end end function set_service(new_service) service = new_service; module.environment.service = service; disco_info = build_disco_info(service); end function module.save() return { service = service }; end function module.restore(data) set_service(data.service); end set_service(pubsub.new({ capabilities = { none = { create = false; publish = false; retract = false; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; publisher = { create = false; publish = true; retract = true; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; owner = { create = true; publish = true; retract = true; delete = true; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = true; unsubscribe_other = true; get_subscription_other = true; get_subscriptions_other = true; be_subscribed = true; be_unsubscribed = true; set_affiliation = true; }; }; autocreate_on_publish = autocreate_on_publish; autocreate_on_subscribe = autocreate_on_subscribe; broadcaster = simple_broadcast; get_affiliation = get_affiliation; normalize_jid = jid_bare; })); prosody-0.9.1/plugins/mod_uptime.lua0000644000175000017500000000275212213321667017463 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local start_time = prosody.start_time; module:hook_global("server-started", function() start_time = prosody.start_time end); -- XEP-0012: Last activity module:add_feature("jabber:iq:last"); module:hook("iq/host/jabber:iq:last:query", function(event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "get" then origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(os.difftime(os.time(), start_time))})); return true; end end); -- Ad-hoc command local adhoc_new = module:require "adhoc".new; function uptime_text() local t = os.time()-prosody.start_time; local seconds = t%60; t = (t - seconds)/60; local minutes = t%60; t = (t - minutes)/60; local hours = t%24; t = (t - hours)/24; local days = t; return string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); end function uptime_command_handler (self, data, state) return { info = uptime_text(), status = "completed" }; end local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler); module:add_item ("adhoc", descriptor); prosody-0.9.1/plugins/mod_motd.lua0000644000175000017500000000176112213321667017122 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2010 Jeff Mitchell -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local motd_text = module:get_option_string("motd_text"); local motd_jid = module:get_option_string("motd_jid", host); if not motd_text then return; end local st = require "util.stanza"; motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config module:hook("presence/bare", function (event) local session, stanza = event.origin, event.stanza; if session.username and not session.presence and not stanza.attr.type and not stanza.attr.to then local motd_stanza = st.message({ to = session.full_jid, from = motd_jid }) :tag("body"):text(motd_text); module:send(motd_stanza); module:log("debug", "MOTD send to user %s", session.full_jid); end end, 1); prosody-0.9.1/plugins/mod_posix.lua0000644000175000017500000001332612213321667017321 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local 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. 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 module:log("warn", "Couldn't load signal library, won't respond to SIGTERM"); end local lfs = require "lfs"; local stat = lfs.attributes; local prosody = _G.prosody; module:set_global(); -- we're a global module local umask = module:get_option("umask") or "027"; pposix.umask(umask); -- Allow switching away from root, some people like strange ports. module:hook("server-started", function () local uid = module:get_option("setuid"); local gid = module:get_option("setgid"); if gid then local success, msg = pposix.setgid(gid); if success then module:log("debug", "Changed group to %s successfully.", gid); else module:log("error", "Failed to change group to %s. Error: %s", gid, msg); prosody.shutdown("Failed to change group to %s", gid); end end if uid then local success, msg = pposix.setuid(uid); if success then module:log("debug", "Changed user to %s successfully.", uid); else module:log("error", "Failed to change user to %s. Error: %s", uid, msg); prosody.shutdown("Failed to change user to %s", uid); end end end); -- Don't even think about it! if not prosody.start_time then -- server-starting local suid = module:get_option("setuid"); if not suid or suid == 0 or suid == "root" then if pposix.getuid() == 0 and not module:get_option("run_as_root") then module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!"); module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root"); prosody.shutdown("Refusing to run as root"); end end end local pidfile; local pidfile_handle; local function remove_pidfile() if pidfile_handle then pidfile_handle:close(); os.remove(pidfile); pidfile, pidfile_handle = nil, nil; end end local function write_pidfile() if pidfile_handle then remove_pidfile(); end pidfile = module:get_option("pidfile"); if pidfile then local err; local mode = stat(pidfile) and "r+" or "w+"; pidfile_handle, err = io.open(pidfile, mode); if not pidfile_handle then module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err); prosody.shutdown("Couldn't write pidfile"); else if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock local other_pid = pidfile_handle:read("*a"); module:log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid); pidfile_handle = nil; prosody.shutdown("Prosody already running"); else pidfile_handle:close(); pidfile_handle, err = io.open(pidfile, "w+"); if not pidfile_handle then module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err); prosody.shutdown("Couldn't write pidfile"); else if lfs.lock(pidfile_handle, "w") then pidfile_handle:write(tostring(pposix.getpid())); pidfile_handle:flush(); end end end end end end local syslog_opened; function syslog_sink_maker(config) if not syslog_opened then pposix.syslog_open("prosody", module:get_option_string("syslog_facility")); syslog_opened = true; end local syslog, format = pposix.syslog_log, string.format; return function (name, level, message, ...) if ... then syslog(level, name, format(message, ...)); else syslog(level, name, message); end end; end require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker); local daemonize = module:get_option("daemonize"); if daemonize == nil then local no_daemonize = module:get_option("no_daemonize"); --COMPAT w/ 0.5 daemonize = not no_daemonize; if no_daemonize ~= nil then module:log("warn", "The 'no_daemonize' option is now replaced by 'daemonize'"); module:log("warn", "Update your config from 'no_daemonize = %s' to 'daemonize = %s'", tostring(no_daemonize), tostring(daemonize)); end end local function remove_log_sinks() local lm = require "core.loggingmanager"; lm.register_sink_type("console", nil); lm.register_sink_type("stdout", nil); lm.reload_logging(); end if daemonize then local function daemonize_server() module:log("info", "Prosody is about to detach from the console, disabling further console output"); remove_log_sinks(); local ok, ret = pposix.daemonize(); if not ok then module:log("error", "Failed to daemonize: %s", ret); elseif ret and ret > 0 then os.exit(0); else module:log("info", "Successfully daemonized to PID %d", pposix.getpid()); write_pidfile(); end end if not prosody.start_time then -- server-starting daemonize_server(); end else -- Not going to daemonize, so write the pid of this process write_pidfile(); end module:hook("server-stopped", remove_pidfile); -- Set signal handlers if signal.signal then signal.signal("SIGTERM", function () module:log("warn", "Received SIGTERM"); prosody.unlock_globals(); prosody.shutdown("Received SIGTERM"); prosody.lock_globals(); end); signal.signal("SIGHUP", function () module:log("info", "Received SIGHUP"); prosody.reload_config(); prosody.reopen_logfiles(); end); signal.signal("SIGINT", function () module:log("info", "Received SIGINT"); prosody.unlock_globals(); prosody.shutdown("Received SIGINT"); prosody.lock_globals(); end); end prosody-0.9.1/plugins/mod_admin_adhoc.lua0000644000175000017500000007540212213321667020410 0ustar matthewmatthew-- Copyright (C) 2009-2011 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local _G = _G; 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; local usermanager_delete_user = require "core.usermanager".delete_user; local usermanager_get_password = require "core.usermanager".get_password; local usermanager_set_password = require "core.usermanager".set_password; local hostmanager_activate = require "core.hostmanager".activate; local hostmanager_deactivate = require "core.hostmanager".deactivate; local rm_load_roster = require "core.rostermanager".load_roster; local st, jid = require "util.stanza", require "util.jid"; local timer_add_task = require "util.timer".add_task; 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; local function generate_error_message(errors) local errmsg = {}; for name, err in pairs(errors) do errmsg[#errmsg + 1] = name .. ": " .. err; end return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end -- 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" }; }; 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 return { status = "completed", error = { message = "Failed to write data to disk" } }; end 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 end); -- 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" }; }; 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 = "completed", error = { message = "User does not exist" } }; end end); -- 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)" }; else return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } }; 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."; { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" }; }; 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 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 "") }; 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; for resource, session in pairs(sessions or {}) do if not givenResource or (resource == givenResource) then module:log("debug", "Disconnecting %s@%s/%s", node, hostname, resource); session:close(); end end return true; end 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" }; }; local end_user_session_handler = adhoc_simple(end_user_session_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 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 "") }; 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 = "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 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 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 }; end); -- Getting user statistics local get_user_stats_layout = dataforms_new{ title = "Get User Statistics"; instructions = "Fill out this form to gather user statistics."; { 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 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 get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err) if err then return generate_error_message(err); end 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[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 end users[#users+1] = " - "..resource..": "..status.."("..priority..")"; end end end return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} }; end); -- 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 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 -- 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:"}; }; 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 = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host.. '". Error was: "'..tostring(err or "")..'"' } }; end 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"; { 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 globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err) local ok_list, err_list = {}, {}; if err then return generate_error_message(err); end local ok, err = modulemanager.load(module_host, fields.module); if ok then ok_list[#ok_list + 1] = module_host; else err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")"; end -- 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 -- 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] = host_name; else 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 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 err then return generate_error_message(err); 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 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 end 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 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 }; end); local function send_to_online(message, server) if server then sessions = { [server] = hosts[server] }; else sessions = hosts; end local c = 0; for domain, session in pairs(sessions) do for user in pairs(session.sessions or {}) do c = c + 1; message.attr.from = domain; message.attr.to = user.."@"..domain; core_post_stanza(session, message); end end return c; end -- 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" }; }; local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err) if err then return generate_error_message(err); 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 timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end); return { status = "completed", info = "Server is about to shut down" }; end); -- Unloading modules local unload_modules_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:"}; }; 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 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 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] = host_name; else err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")"; end end end 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 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); -- Activating a host local activate_host_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:"}; }; 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); if ok then return { status = "completed", info = fields.host .. " activated" }; else return { status = "canceled", error = err } end end); -- Deactivating a host local deactivate_host_layout = dataforms_new { title = "Deactivate host"; instructions = ""; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; { name = "host", type = "text-single", required = true, label = "Host:"}; }; local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err) 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 end); local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin"); local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin"); local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin"); local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin"); local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin"); local get_user_password_desc = adhoc_new("Get User Password", "http://jabber.org/protocol/admin#get-user-password", get_user_password_handler, "admin"); local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin"); local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin"); local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users-list", get_online_users_command_handler, "admin"); local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin"); local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin"); local globally_load_module_desc = adhoc_new("Globally load module", "http://prosody.im/protocol/modules#global-load", globally_load_module_handler, "global_admin"); local reload_modules_desc = adhoc_new("Reload modules", "http://prosody.im/protocol/modules#reload", reload_modules_handler, "admin"); local globally_reload_module_desc = adhoc_new("Globally reload module", "http://prosody.im/protocol/modules#global-reload", globally_reload_module_handler, "global_admin"); local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "global_admin"); local unload_modules_desc = adhoc_new("Unload modules", "http://prosody.im/protocol/modules#unload", unload_modules_handler, "admin"); local globally_unload_module_desc = adhoc_new("Globally unload module", "http://prosody.im/protocol/modules#global-unload", globally_unload_module_handler, "global_admin"); local activate_host_desc = adhoc_new("Activate host", "http://prosody.im/protocol/hosts#activate", activate_host_handler, "global_admin"); local deactivate_host_desc = adhoc_new("Deactivate host", "http://prosody.im/protocol/hosts#deactivate", deactivate_host_handler, "global_admin"); module:provides("adhoc", add_user_desc); module:provides("adhoc", change_user_password_desc); module:provides("adhoc", config_reload_desc); module:provides("adhoc", delete_user_desc); module:provides("adhoc", end_user_session_desc); module:provides("adhoc", get_user_password_desc); module:provides("adhoc", get_user_roster_desc); module:provides("adhoc", get_user_stats_desc); module:provides("adhoc", get_online_users_desc); module:provides("adhoc", list_modules_desc); module:provides("adhoc", load_module_desc); module:provides("adhoc", globally_load_module_desc); module:provides("adhoc", reload_modules_desc); module:provides("adhoc", globally_reload_module_desc); module:provides("adhoc", shut_down_service_desc); module:provides("adhoc", unload_modules_desc); module:provides("adhoc", globally_unload_module_desc); module:provides("adhoc", activate_host_desc); module:provides("adhoc", deactivate_host_desc); prosody-0.9.1/plugins/mod_http.lua0000644000175000017500000001106012213321667017127 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2012 Matthew Wild -- Copyright (C) 2008-2012 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); module:depends("http_errors"); local portmanager = require "core.portmanager"; local moduleapi = require "core.moduleapi"; local url_parse = require "socket.url".parse; local url_build = require "socket.url".build; local server = require "net.http.server"; server.set_default_host(module:get_option_string("http_default_host")); local function normalize_path(path) if path:sub(-1,-1) == "/" then path = path:sub(1, -2); end if path:sub(1,1) ~= "/" then path = "/"..path; end return path; end local function get_http_event(host, app_path, key) local method, path = key:match("^(%S+)%s+(.+)$"); if not method then -- No path specified, default to "" (base path) method, path = key, ""; end if method:sub(1,1) == "/" then return nil; end if app_path == "/" and path:sub(1,1) == "/" then app_path = ""; end return method:upper().." "..host..app_path..path; end local function get_base_path(host_module, app_name, default_app_path) return (normalize_path(host_module:get_option("http_paths", {})[app_name] -- Host or module:get_option("http_paths", {})[app_name] -- Global or default_app_path)) -- Default :gsub("%$(%w+)", { host = module.host }); end local ports_by_scheme = { http = 80, https = 443, }; -- Helper to deduce a module's external URL function moduleapi.http_url(module, app_name, default_path) app_name = app_name or (module.name:gsub("^http_", "")); local external_url = url_parse(module:get_option_string("http_external_url")) or {}; local services = portmanager.get_active_services(); local http_services = services:get("https") or services:get("http") or {}; for interface, ports in pairs(http_services) do for port, services in pairs(ports) do local url = { scheme = (external_url.scheme or services[1].service.name); host = (external_url.host or module:get_option_string("http_host", module.host)); port = tonumber(external_url.port) or port or 80; path = normalize_path(external_url.path or "/").. (get_base_path(module, app_name, default_path or "/"..app_name):sub(2)); } if ports_by_scheme[url.scheme] == url.port then url.port = nil end return url_build(url); end end end function module.add_host(module) local host = module:get_option_string("http_host", module.host); local apps = {}; module.environment.apps = apps; local function http_app_added(event) local app_name = event.item.name; local default_app_path = event.item.default_path or "/"..app_name; local app_path = get_base_path(module, app_name, default_app_path); if not app_name then -- TODO: Link to docs module:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)"); return; end apps[app_name] = apps[app_name] or {}; local app_handlers = apps[app_name]; for key, handler in pairs(event.item.route or {}) do local event_name = get_http_event(host, app_path, key); if event_name then if type(handler) ~= "function" then local data = handler; handler = function () return data; end elseif event_name:sub(-2, -1) == "/*" then local base_path_len = #event_name:match("/.+$"); local _handler = handler; handler = function (event) local path = event.request.path:sub(base_path_len); return _handler(event, path); end; end if not app_handlers[event_name] then app_handlers[event_name] = handler; module:hook_object_event(server, event_name, handler); else module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name); end else module:log("error", "Invalid route in %s, %q. See http://prosody.im/doc/developers/http#routes", app_name, key); end end end local function http_app_removed(event) local app_handlers = apps[event.item.name]; apps[event.item.name] = nil; for event, handler in pairs(app_handlers) do module:unhook_object_event(server, event, handler); end end module:handle_items("http-provider", http_app_added, http_app_removed); server.add_host(host); function module.unload() server.remove_host(host); end end module:provides("net", { name = "http"; listener = server.listener; default_port = 5280; multiplex = { pattern = "^[A-Z]"; }; }); module:provides("net", { name = "https"; listener = server.listener; default_port = 5281; encryption = "ssl"; ssl_config = { verify = "none" }; multiplex = { pattern = "^[A-Z]"; }; }); prosody-0.9.1/plugins/adhoc/0000775000175000017500000000000012213321667015670 5ustar matthewmatthewprosody-0.9.1/plugins/adhoc/adhoc.lib.lua0000644000175000017500000000543112213321667020217 0ustar matthewmatthew-- Copyright (C) 2009-2010 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st, uuid = require "util.stanza", require "util.uuid"; local xmlns_cmd = "http://jabber.org/protocol/commands"; local states = {} local _M = {}; local function _cmdtag(desc, status, sessionid, action) local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status }); if sessionid then cmd.attr.sessionid = sessionid; end if action then cmd.attr.action = action; end return cmd; end function _M.new(name, node, handler, permission) return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") }; end function _M.handle_cmd(command, origin, stanza) local sessionid = stanza.tags[1].attr.sessionid or uuid.generate(); local dataIn = {}; dataIn.to = stanza.attr.to; dataIn.from = stanza.attr.from; dataIn.action = stanza.tags[1].attr.action or "execute"; dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data"); local data, state = command:handler(dataIn, states[sessionid]); states[sessionid] = state; local cmdtag; if data.status == "completed" then states[sessionid] = nil; cmdtag = command:cmdtag("completed", sessionid); elseif data.status == "canceled" then states[sessionid] = nil; cmdtag = command:cmdtag("canceled", sessionid); elseif data.status == "error" then states[sessionid] = nil; local reply = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message); origin.send(reply); return true; else cmdtag = command:cmdtag("executing", sessionid); data.actions = data.actions or { "complete" }; end for name, content in pairs(data) do if name == "info" then cmdtag:tag("note", {type="info"}):text(content):up(); elseif name == "warn" then cmdtag:tag("note", {type="warn"}):text(content):up(); elseif name == "error" then cmdtag:tag("note", {type="error"}):text(content.message):up(); elseif name == "actions" then local actions = st.stanza("actions", { execute = content.default }); for _, action in ipairs(content) do if (action == "prev") or (action == "next") or (action == "complete") then actions:tag(action):up(); else module:log("error", "Command %q at node %q provided an invalid action %q", command.name, command.node, action); end end cmdtag:add_child(actions); elseif name == "form" then cmdtag:add_child((content.layout or content):form(content.values)); elseif name == "result" then cmdtag:add_child((content.layout or content):form(content.values, "result")); elseif name == "other" then cmdtag:add_child(content); end end local reply = st.reply(stanza); reply:add_child(cmdtag); origin.send(reply); return true; end return _M; prosody-0.9.1/plugins/adhoc/mod_adhoc.lua0000644000175000017500000000723012213321667020310 0ustar matthewmatthew-- Copyright (C) 2009 Thilo Cestonaro -- Copyright (C) 2009-2011 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza"; local is_admin = require "core.usermanager".is_admin; local adhoc_handle_cmd = module:require "adhoc".handle_cmd; local xmlns_cmd = "http://jabber.org/protocol/commands"; local xmlns_disco = "http://jabber.org/protocol/disco"; local commands = {}; module:add_feature(xmlns_cmd); module:hook("iq/host/"..xmlns_disco.."#info:query", function (event) local origin, stanza = event.origin, event.stanza; local node = stanza.tags[1].attr.node; if stanza.attr.type == "get" and node then if commands[node] then local privileged = is_admin(stanza.attr.from, stanza.attr.to); if (commands[node].permission == "admin" and privileged) or (commands[node].permission == "user") then reply = st.reply(stanza); reply:tag("query", { xmlns = xmlns_disco.."#info", node = node }); reply:tag("identity", { name = commands[node].name, category = "automation", type = "command-node" }):up(); reply:tag("feature", { var = xmlns_cmd }):up(); reply:tag("feature", { var = "jabber:x:data" }):up(); else reply = st.error_reply(stanza, "auth", "forbidden", "This item is not available to you"); end origin.send(reply); return true; elseif node == xmlns_cmd then reply = st.reply(stanza); reply:tag("query", { xmlns = xmlns_disco.."#info", node = node }); reply:tag("identity", { name = "Ad-Hoc Commands", category = "automation", type = "command-list" }):up(); origin.send(reply); return true; end end end); module:hook("iq/host/"..xmlns_disco.."#items:query", function (event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "get" and stanza.tags[1].attr.node and stanza.tags[1].attr.node == xmlns_cmd then local admin = is_admin(stanza.attr.from, stanza.attr.to); local global_admin = is_admin(stanza.attr.from); reply = st.reply(stanza); reply:tag("query", { xmlns = xmlns_disco.."#items", node = xmlns_cmd }); for node, command in pairs(commands) do if (command.permission == "admin" and admin) or (command.permission == "global_admin" and global_admin) or (command.permission == "user") then reply:tag("item", { name = command.name, node = node, jid = module:get_host() }); reply:up(); end end origin.send(reply); return true; end end, 500); module:hook("iq/host/"..xmlns_cmd..":command", function (event) local origin, stanza = event.origin, event.stanza; if stanza.attr.type == "set" then local node = stanza.tags[1].attr.node if commands[node] then local admin = is_admin(stanza.attr.from, stanza.attr.to); local global_admin = is_admin(stanza.attr.from); if (commands[node].permission == "admin" and not admin) or (commands[node].permission == "global_admin" and not global_admin) then origin.send(st.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up() :add_child(commands[node]:cmdtag("canceled") :tag("note", {type="error"}):text("You don't have permission to execute this command"))); return true end -- User has permission now execute the command return adhoc_handle_cmd(commands[node], origin, stanza); end end end, 500); local function adhoc_added(event) local item = event.item; commands[item.node] = item; end local function adhoc_removed(event) commands[event.item.node] = nil; end module:handle_items("adhoc", adhoc_added, adhoc_removed); module:handle_items("adhoc-provider", adhoc_added, adhoc_removed); prosody-0.9.1/plugins/mod_component.lua0000644000175000017500000002543612213321667020166 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local t_concat = table.concat; local xpcall, tostring, type = xpcall, tostring, type; local traceback = debug.traceback; local logger = require "util.logger"; local sha1 = require "util.hashes".sha1; local st = require "util.stanza"; local jid_split = require "util.jid".split; local new_xmpp_stream = require "util.xmppstream".new; local uuid_gen = require "util.uuid".generate; local core_process_stanza = prosody.core_process_stanza; local hosts = prosody.hosts; local log = module._log; local sessions = module:shared("sessions"); function module.add_host(module) if module:get_host_type() ~= "component" then error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0); end local env = module.environment; env.connected = false; local send; local function on_destroy(session, err) env.connected = false; send = nil; session.on_destroy = nil; end -- Handle authentication attempts by component local function handle_component_auth(event) local session, stanza = event.origin, event.stanza; if session.type ~= "component_unauthed" then return; end if (not session.host) or #stanza.tags > 0 then (session.log or log)("warn", "Invalid component handshake for host: %s", session.host); session:close("not-authorized"); return true; end local secret = module:get_option("component_secret"); if not secret then (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host); session:close("not-authorized"); return true; end local supplied_token = t_concat(stanza); local calculated_token = sha1(session.streamid..secret, true); if supplied_token:lower() ~= calculated_token:lower() then module:log("info", "Component authentication failed for %s", session.host); session:close{ condition = "not-authorized", text = "Given token does not match calculated token" }; return true; end if env.connected then module:log("error", "Second component attempted to connect, denying connection"); session:close{ condition = "conflict", text = "Component already connected" }; return true; end env.connected = true; send = session.send; session.on_destroy = on_destroy; session.component_validate_from = module:get_option_boolean("validate_from_addresses", true); session.type = "component"; module:log("info", "External component successfully authenticated"); session.send(st.stanza("handshake")); return true; end module:hook("stanza/jabber:component:accept:handshake", handle_component_auth); -- Handle stanzas addressed to this component local function handle_stanza(event) local stanza = event.stanza; if send then stanza.attr.xmlns = nil; send(stanza); else if stanza.name == "iq" and stanza.attr.type == "get" and stanza.attr.to == module.host then local query = stanza.tags[1]; local node = query.attr.node; if query.name == "query" and query.attr.xmlns == "http://jabber.org/protocol/disco#info" and (not node or node == "") then local name = module:get_option_string("name"); if name then event.origin.send(st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info" }) :tag("identity", { category = "component", type = "generic", name = module:get_option_string("name", "Prosody") })) return true; end end end module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag()); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable")); end end return true; end module:hook("iq/bare", handle_stanza, -1); module:hook("message/bare", handle_stanza, -1); module:hook("presence/bare", handle_stanza, -1); module:hook("iq/full", handle_stanza, -1); module:hook("message/full", handle_stanza, -1); module:hook("presence/full", handle_stanza, -1); module:hook("iq/host", handle_stanza, -1); module:hook("message/host", handle_stanza, -1); module:hook("presence/host", handle_stanza, -1); end --- Network and stream part --- local xmlns_component = 'jabber:component:accept'; local listener = {}; --- Callbacks/data for xmppstream to handle streams for us --- local stream_callbacks = { default_ns = xmlns_component }; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; function stream_callbacks.error(session, error, data, data2) if session.destroyed then return; end module:log("warn", "Error processing component stream: %s", tostring(error)); if error == "no-stream" then session:close("invalid-namespace"); elseif error == "parse-error" then session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data)); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:children() do if child.attr.xmlns == xmlns_xmpp_streams then if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end end function stream_callbacks.streamopened(session, attr) if not hosts[attr.to] or not hosts[attr.to].modules.component then session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" }; return; end session.host = attr.to; session.streamid = uuid_gen(); session.notopen = nil; -- Return stream header session.send(""); session.send(st.stanza("stream:stream", { xmlns=xmlns_component, ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag()); end function stream_callbacks.streamclosed(session) session.log("debug", "Received "); session:close(); end local function handleerr(err) log("error", "Traceback[component]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(session, stanza) -- Namespaces are icky. if not stanza.attr.xmlns and stanza.name == "handshake" then stanza.attr.xmlns = xmlns_component; end if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then local from = stanza.attr.from; if from then if session.component_validate_from then local _, domain = jid_split(stanza.attr.from); if domain ~= session.host then -- Return error session.log("warn", "Component sent stanza with missing or invalid 'from' address"); session:close{ condition = "invalid-from"; text = "Component tried to send from address <"..tostring(from) .."> which is not in domain <"..tostring(session.host)..">"; }; return; end end else stanza.attr.from = session.host; -- COMPAT: Strictly we shouldn't allow this end if not stanza.attr.to then session.log("warn", "Rejecting stanza with no 'to' address"); session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas")); return; end end if stanza then return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); end end --- Closing a component connection local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; local function session_close(session, reason) if session.destroyed then return; end if session.conn then if session.notopen then session.send(""); session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); end if reason then if type(reason) == "string" then -- assume stream error module:log("info", "Disconnecting component, is: %s", reason); session.send(st.stanza("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(); if reason.text then stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stanza:add_child(reason.extra); end module:log("info", "Disconnecting component, is: %s", tostring(stanza)); session.send(stanza); elseif reason.name then -- a stanza module:log("info", "Disconnecting component, is: %s", tostring(reason)); session.send(reason); end end end session.send(""); session.conn:close(); listener.ondisconnect(session.conn, "stream error"); end end --- Component connlistener function listener.onconnect(conn) local _send = conn.write; local session = { type = "component_unauthed", conn = conn, send = function (data) return _send(conn, tostring(data)); end }; -- Logging functions -- local conn_name = "jcp"..tostring(session):match("[a-f0-9]+$"); session.log = logger.init(conn_name); session.close = session_close; session.log("info", "Incoming Jabber component connection"); local stream = new_xmpp_stream(session, stream_callbacks); session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.stream:reset(); end function session.data(conn, data) local ok, err = stream:feed(data); if ok then return; end module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); session:close("not-well-formed"); end session.dispatch_stanza = stream_callbacks.handlestanza; sessions[conn] = session; end function listener.onincoming(conn, data) local session = sessions[conn]; session.data(conn, data); end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err)); if session.on_destroy then session:on_destroy(err); end sessions[conn] = nil; for k in pairs(session) do if k ~= "log" and k ~= "close" then session[k] = nil; end end session.destroyed = true; session = nil; end end module:provides("net", { name = "component"; private = true; listener = listener; default_port = 5347; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:component:accept%1.*>"; }; }); prosody-0.9.1/plugins/mod_privacy.lua0000644000175000017500000003552012213321667017634 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2009-2010 Matthew Wild -- Copyright (C) 2009-2010 Waqas Hussain -- Copyright (C) 2009 Thilo Cestonaro -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:add_feature("jabber:iq:privacy"); local st = require "util.stanza"; local bare_sessions, full_sessions = prosody.bare_sessions, prosody.full_sessions; local util_Jid = require "util.jid"; local jid_bare = util_Jid.bare; 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 for resource, session in pairs(user.sessions) do if resource ~= origin.resource then if session.activePrivacyList == name then return true; elseif session.activePrivacyList == nil and privacy_lists.default == name then return true; end end end end end function isAnotherSessionUsingDefaultList(origin) local user = bare_sessions[origin.username.."@"..origin.host]; if user then for resource, session in pairs(user.sessions) do if resource ~= origin.resource and session.activePrivacyList == nil then return true; end end end end function declineList(privacy_lists, origin, stanza, which) if which == "default" then if isAnotherSessionUsingDefaultList(origin) then return { "cancel", "conflict", "Another session is online and using the default list."}; end privacy_lists.default = nil; origin.send(st.reply(stanza)); elseif which == "active" then origin.activePrivacyList = nil; origin.send(st.reply(stanza)); else return {"modify", "bad-request", "Neither default nor active list specifed to decline."}; end return true; end function activateList(privacy_lists, origin, stanza, which, name) local list = privacy_lists.lists[name]; if which == "default" and list then if isAnotherSessionUsingDefaultList(origin) then return {"cancel", "conflict", "Another session is online and using the default list."}; end privacy_lists.default = name; origin.send(st.reply(stanza)); elseif which == "active" and list then origin.activePrivacyList = name; origin.send(st.reply(stanza)); elseif not list then return {"cancel", "item-not-found", "No such list: "..name}; else return {"modify", "bad-request", "No list chosen to be active or default."}; end return true; end function deleteList(privacy_lists, origin, stanza, name) local list = privacy_lists.lists[name]; if list then if isListUsed(origin, name, privacy_lists) then return {"cancel", "conflict", "Another session is online and using the list which should be deleted."}; end if privacy_lists.default == name then privacy_lists.default = nil; end if origin.activePrivacyList == name then origin.activePrivacyList = nil; end privacy_lists.lists[name] = nil; origin.send(st.reply(stanza)); return true; end return {"modify", "bad-request", "Not existing list specifed to be deleted."}; end function createOrReplaceList (privacy_lists, origin, stanza, name, entries) local bare_jid = origin.username.."@"..origin.host; if privacy_lists.lists == nil then privacy_lists.lists = {}; end local list = {}; privacy_lists.lists[name] = list; local orderCheck = {}; list.name = name; list.items = {}; for _,item in ipairs(entries) do if to_number(item.attr.order) == nil or to_number(item.attr.order) < 0 or orderCheck[item.attr.order] ~= nil then return {"modify", "bad-request", "Order attribute not valid."}; end if item.attr.type ~= nil and item.attr.type ~= "jid" and item.attr.type ~= "subscription" and item.attr.type ~= "group" then return {"modify", "bad-request", "Type attribute not valid."}; end local tmp = {}; orderCheck[item.attr.order] = true; tmp["type"] = item.attr.type; tmp["value"] = item.attr.value; tmp["action"] = item.attr.action; tmp["order"] = to_number(item.attr.order); tmp["presence-in"] = false; tmp["presence-out"] = false; tmp["message"] = false; tmp["iq"] = false; if #item.tags > 0 then for _,tag in ipairs(item.tags) do tmp[tag.name] = true; end end if tmp.type == "subscription" then if tmp.value ~= "both" and tmp.value ~= "to" and tmp.value ~= "from" and tmp.value ~= "none" then return {"cancel", "bad-request", "Subscription value must be both, to, from or none."}; end end if tmp.action ~= "deny" and tmp.action ~= "allow" then return {"cancel", "bad-request", "Action must be either deny or allow."}; end list.items[#list.items + 1] = tmp; end table.sort(list, function(a, b) return a.order < b.order; end); origin.send(st.reply(stanza)); if bare_sessions[bare_jid] ~= nil then local iq = st.iq ( { type = "set", id="push1" } ); iq:tag ("query", { xmlns = "jabber:iq:privacy" } ); iq:tag ("list", { name = list.name } ):up(); iq:up(); for resource, session in pairs(bare_sessions[bare_jid].sessions) do iq.attr.to = bare_jid.."/"..resource session.send(iq); end else return {"cancel", "bad-request", "internal error."}; end return true; end function getList(privacy_lists, origin, stanza, name) local reply = st.reply(stanza); reply:tag("query", {xmlns="jabber:iq:privacy"}); if name == nil then if privacy_lists.lists then if origin.activePrivacyList then reply:tag("active", {name=origin.activePrivacyList}):up(); end if privacy_lists.default then reply:tag("default", {name=privacy_lists.default}):up(); end for name,list in pairs(privacy_lists.lists) do reply:tag("list", {name=name}):up(); end end else local list = privacy_lists.lists[name]; if list then reply = reply:tag("list", {name=list.name}); for _,item in ipairs(list.items) do reply:tag("item", {type=item.type, value=item.value, action=item.action, order=item.order}); if item["message"] then reply:tag("message"):up(); end if item["iq"] then reply:tag("iq"):up(); end if item["presence-in"] then reply:tag("presence-in"):up(); end if item["presence-out"] then reply:tag("presence-out"):up(); end reply:up(); end else return {"cancel", "item-not-found", "Unknown list specified."}; end end origin.send(reply); return true; end module:hook("iq/bare/jabber:iq:privacy:query", function(data) local origin, stanza = data.origin, data.stanza; 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 = 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); local lists = privacy_lists.lists; for idx, list in ipairs(lists) do lists[list.name] = list; lists[idx] = nil; end end if stanza.attr.type == "set" then if #query.tags == 1 then -- the element MUST NOT include more than one child element for _,tag in ipairs(query.tags) do if tag.name == "active" or tag.name == "default" then if tag.attr.name == nil then -- Client declines the use of active / default list valid = declineList(privacy_lists, origin, stanza, tag.name); else -- Client requests change of active / default list valid = activateList(privacy_lists, origin, stanza, tag.name, tag.attr.name); end elseif tag.name == "list" and tag.attr.name then -- Client adds / edits a privacy list if #tag.tags == 0 then -- Client removes a privacy list valid = deleteList(privacy_lists, origin, stanza, tag.attr.name); else -- Client edits a privacy list valid = createOrReplaceList(privacy_lists, origin, stanza, tag.attr.name, tag.tags); end end end end elseif stanza.attr.type == "get" then local name = nil; local listsToRetrieve = 0; if #query.tags >= 1 then for _,tag in ipairs(query.tags) do if tag.name == "list" then -- Client requests a privacy list from server name = tag.attr.name; listsToRetrieve = listsToRetrieve + 1; end end end if listsToRetrieve == 0 or listsToRetrieve == 1 then valid = getList(privacy_lists, origin, stanza, name); end end if valid ~= true then valid = valid or { "cancel", "bad-request", "Couldn't understand request" }; if valid[1] == nil then valid[1] = "cancel"; end if valid[2] == nil then valid[2] = "bad-request"; end origin.send(st.error_reply(stanza, valid[1], valid[2], valid[3])); else privacy_storage:set(origin.username, privacy_lists); end return true; end end); function checkIfNeedToBeBlocked(e, session) local origin, stanza = e.origin, e.stanza; 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; local is_to_user = bare_jid == jid_bare(to); local is_from_user = bare_jid == jid_bare(from); --module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from)); if privacy_lists.lists == nil or not (session.activePrivacyList or privacy_lists.default) then return; -- Nothing to block, default is Allow all end if is_from_user and is_to_user then --module:log("debug", "Not blocking communications between user's resources"); return; -- from one of a user's resource to another => HANDS OFF! end local listname = session.activePrivacyList; if listname == nil then listname = privacy_lists.default; -- no active list selected, use default list end local list = privacy_lists.lists[listname]; if not list then -- should never happen module:log("warn", "given privacy list not found. name: %s for user %s", listname, bare_jid); return; end for _,item in ipairs(list.items) do local apply = false; local block = false; if ( (stanza.name == "message" and item.message) or (stanza.name == "iq" and item.iq) or (stanza.name == "presence" and is_to_user and item["presence-in"]) or (stanza.name == "presence" and is_from_user and item["presence-out"]) or (item.message == false and item.iq == false and item["presence-in"] == false and item["presence-out"] == false) ) then apply = true; end if apply then local evilJid = {}; apply = false; if is_to_user then --module:log("debug", "evil jid is (from): %s", from); evilJid.node, evilJid.host, evilJid.resource = jid_split(from); else --module:log("debug", "evil jid is (to): %s", to); evilJid.node, evilJid.host, evilJid.resource = jid_split(to); end if item.type == "jid" and (evilJid.node and evilJid.host and evilJid.resource and item.value == evilJid.node.."@"..evilJid.host.."/"..evilJid.resource) or (evilJid.node and evilJid.host and item.value == evilJid.node.."@"..evilJid.host) or (evilJid.host and evilJid.resource and item.value == evilJid.host.."/"..evilJid.resource) or (evilJid.host and item.value == evilJid.host) then apply = true; block = (item.action == "deny"); elseif item.type == "group" then local roster = load_roster(session.username, session.host); local roster_entry = roster[jid_join(evilJid.node, evilJid.host)]; if roster_entry then local groups = roster_entry.groups; for group in pairs(groups) do if group == item.value then apply = true; block = (item.action == "deny"); break; end end end elseif item.type == "subscription" then -- we need a valid bare evil jid local roster = load_roster(session.username, session.host); local roster_entry = roster[jid_join(evilJid.node, evilJid.host)]; if (not(roster_entry) and item.value == "none") or (roster_entry and roster_entry.subscription == item.value) then apply = true; block = (item.action == "deny"); end elseif item.type == nil then apply = true; block = (item.action == "deny"); end end if apply then if block then -- drop and not bounce groupchat messages, otherwise users will get kicked if stanza.attr.type == "groupchat" then return true; end module:log("debug", "stanza blocked: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from)); if stanza.name == "message" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); elseif stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end return true; -- stanza blocked ! else --module:log("debug", "stanza explicitly allowed!") return; end end end end function preCheckIncoming(e) local session; if e.stanza.attr.to ~= nil then local node, host, resource = jid_split(e.stanza.attr.to); if node == nil or host == nil then return; end if resource == nil then local prio = 0; if bare_sessions[node.."@"..host] ~= nil then for resource, session_ in pairs(bare_sessions[node.."@"..host].sessions) do if session_.priority ~= nil and session_.priority > prio then session = session_; prio = session_.priority; end end end else session = full_sessions[node.."@"..host.."/"..resource]; end if session ~= nil then return checkIfNeedToBeBlocked(e, session); else --module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource)); end end end function preCheckOutgoing(e) local session = e.origin; if e.stanza.attr.from == nil then e.stanza.attr.from = session.username .. "@" .. session.host; if session.resource ~= nil then e.stanza.attr.from = e.stanza.attr.from .. "/" .. session.resource; end end if session.username then -- FIXME do properly return checkIfNeedToBeBlocked(e, session); end end module:hook("pre-message/full", preCheckOutgoing, 500); module:hook("pre-message/bare", preCheckOutgoing, 500); module:hook("pre-message/host", preCheckOutgoing, 500); module:hook("pre-iq/full", preCheckOutgoing, 500); module:hook("pre-iq/bare", preCheckOutgoing, 500); module:hook("pre-iq/host", preCheckOutgoing, 500); module:hook("pre-presence/full", preCheckOutgoing, 500); module:hook("pre-presence/bare", preCheckOutgoing, 500); module:hook("pre-presence/host", preCheckOutgoing, 500); module:hook("message/full", preCheckIncoming, 500); module:hook("message/bare", preCheckIncoming, 500); module:hook("message/host", preCheckIncoming, 500); module:hook("iq/full", preCheckIncoming, 500); module:hook("iq/bare", preCheckIncoming, 500); module:hook("iq/host", preCheckIncoming, 500); module:hook("presence/full", preCheckIncoming, 500); module:hook("presence/bare", preCheckIncoming, 500); module:hook("presence/host", preCheckIncoming, 500); prosody-0.9.1/plugins/mod_dialback.lua0000644000175000017500000001442012213321667017705 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local hosts = _G.hosts; local log = module._log; local st = require "util.stanza"; local sha256_hash = require "util.hashes".sha256; local nameprep = require "util.encodings".stringprep.nameprep; local xmlns_stream = "http://etherx.jabber.org/streams"; local dialback_requests = setmetatable({}, { __mode = 'v' }); function generate_dialback(id, to, from) return sha256_hash(id..to..from..hosts[from].dialback_secret, true); end function initiate_dialback(session) -- generate dialback key session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host); session.sends2s(st.stanza("db:result", { from = session.from_host, to = session.to_host }):text(session.dialback_key)); session.log("info", "sent dialback key on outgoing s2s stream"); end function verify_dialback(id, to, from, key) return key == generate_dialback(id, to, from); end module:hook("stanza/jabber:server:dialback:verify", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then -- We are being asked to verify the key, to ensure it was generated by us origin.log("debug", "verifying that dialback key is ours..."); local attr = stanza.attr; if attr.type then module:log("warn", "Ignoring incoming session from %s claiming a dialback key for %s is %s", origin.from_host or "(unknown)", attr.from or "(unknown)", attr.type); return true; end -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34 --if attr.from ~= origin.to_host then error("invalid-from"); end local type; if verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then type = "valid" else type = "invalid" origin.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to); end origin.log("debug", "verified dialback key... it is %s", type); origin.sends2s(st.stanza("db:verify", { from = attr.to, to = attr.from, id = attr.id, type = type }):text(stanza[1])); return true; end end); module:hook("stanza/jabber:server:dialback:result", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then -- he wants to be identified through dialback -- We need to check the key with the Authoritative server local attr = stanza.attr; local to, from = nameprep(attr.to), nameprep(attr.from); if not hosts[to] then -- Not a host that we serve origin.log("info", "%s tried to connect to %s, which we don't serve", from, to); origin:close("host-unknown"); return true; elseif not from then origin:close("improper-addressing"); end origin.hosts[from] = { dialback_key = stanza[1] }; dialback_requests[from.."/"..origin.streamid] = origin; -- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from' -- on streams. We fill in the session's to/from here instead. if not origin.from_host then origin.from_host = from; end if not origin.to_host then origin.to_host = to; end origin.log("debug", "asking %s if key %s belongs to them", from, stanza[1]); module:fire_event("route/remote", { from_host = to, to_host = from; stanza = st.stanza("db:verify", { from = to, to = from, id = origin.streamid }):text(stanza[1]); }); return true; end end); module:hook("stanza/jabber:server:dialback:verify", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then local attr = stanza.attr; local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")]; if dialback_verifying and attr.from == origin.to_host then local valid; if attr.type == "valid" then module:fire_event("s2s-authenticated", { session = dialback_verifying, host = attr.from }); valid = "valid"; else -- Warn the original connection that is was not verified successfully log("warn", "authoritative server for %s denied the key", attr.from or "(unknown)"); valid = "invalid"; end if dialback_verifying.destroyed then log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the db result", tostring(dialback_verifying):match("%w+$")); else dialback_verifying.sends2s( st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid }) :text(dialback_verifying.hosts[attr.from].dialback_key)); end dialback_requests[attr.from.."/"..(attr.id or "")] = nil; end return true; end end); module:hook("stanza/jabber:server:dialback:result", function(event) local origin, stanza = event.origin, event.stanza; if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then -- Remote server is telling us whether we passed dialback local attr = stanza.attr; if not hosts[attr.to] then origin:close("host-unknown"); return true; elseif hosts[attr.to].s2sout[attr.from] ~= origin then -- This isn't right origin:close("invalid-id"); return true; end if stanza.attr.type == "valid" then module:fire_event("s2s-authenticated", { session = origin, host = attr.from }); else origin:close("not-authorized", "dialback authentication failed"); end return true; end end); module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza) if origin.external_auth == "failed" then module:log("debug", "SASL EXTERNAL failed, falling back to dialback"); initiate_dialback(origin); return true; end end, 100); module:hook_stanza(xmlns_stream, "features", function (origin, stanza) if not origin.external_auth or origin.external_auth == "failed" then module:log("debug", "Initiating dialback..."); initiate_dialback(origin); return true; end end, 100); module:hook("s2sout-authenticate-legacy", function (event) module:log("debug", "Initiating dialback..."); initiate_dialback(event.origin); return true; end, 100); -- Offer dialback to incoming hosts module:hook("s2s-stream-features", function (data) data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up(); end); prosody-0.9.1/plugins/mod_offline.lua0000644000175000017500000000315712213321667017602 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2009 Matthew Wild -- Copyright (C) 2008-2009 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local datamanager = require "util.datamanager"; local st = require "util.stanza"; local datetime = require "util.datetime"; local ipairs = ipairs; local jid_split = require "util.jid".split; module:add_feature("msgoffline"); module:hook("message/offline/handle", function(event) local origin, stanza = event.origin, event.stanza; local to = stanza.attr.to; local node, host; if to then node, host = jid_split(to) else node, host = origin.username, origin.host; end stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy(); local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza)); stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil; return result; end); module:hook("message/offline/broadcast", function(event) local origin = event.origin; local node, host = origin.username, origin.host; local data = datamanager.list_load(node, host, "offline"); if not data then return true; end for _, stanza in ipairs(data) do stanza = st.deserialize(stanza); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated) stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil; origin.send(stanza); end datamanager.list_store(node, host, "offline", nil); return true; end); prosody-0.9.1/plugins/mod_vcard.lua0000644000175000017500000000316712213321667017260 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local st = require "util.stanza" local jid_split = require "util.jid".split; local vcards = module:open_store(); module:add_feature("vcard-temp"); local function handle_vcard(event) local session, stanza = event.origin, event.stanza; local to = stanza.attr.to; if stanza.attr.type == "get" then local vCard; if to then local node, host = jid_split(to); vCard = st.deserialize(vcards:get(node)); -- load vCard for user or server else 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! else session.send(st.error_reply(stanza, "cancel", "item-not-found")); end else if not to 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? session.send(st.error_reply(stanza, "wait", "internal-server-error")); end else session.send(st.error_reply(stanza, "auth", "forbidden")); end end return true; end module:hook("iq/bare/vcard-temp:vCard", handle_vcard); module:hook("iq/host/vcard-temp:vCard", handle_vcard); -- COMPAT w/0.8 if module:get_option("vcard_compatibility") ~= nil then module:log("error", "The vcard_compatibility option has been removed, see".. "mod_compat_vcard in prosody-modules if you still need this."); end prosody-0.9.1/plugins/muc/0000775000175000017500000000000012213321667015376 5ustar matthewmatthewprosody-0.9.1/plugins/muc/muc.lib.lua0000644000175000017500000012647612213321667017450 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local select = select; local pairs, ipairs = pairs, ipairs; local datetime = require "util.datetime"; local dataform = require "util.dataforms"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local st = require "util.stanza"; local log = require "util.logger".init("mod_muc"); local t_insert, t_remove = table.insert, table.remove; local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; local muc_domain = nil; --module:get_host(); local default_history_length, max_history_length = 20, math.huge; ------------ local function filter_xmlns_from_array(array, filters) local count = 0; for i=#array,1,-1 do local attr = array[i].attr; if filters[attr and attr.xmlns] then t_remove(array, i); count = count + 1; end end return count; end local function filter_xmlns_from_stanza(stanza, filters) if filters then if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then return stanza, filter_xmlns_from_array(stanza, filters); end end return stanza, 0; end local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; local function get_filtered_presence(stanza) return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters); end local kickable_error_conditions = { ["gone"] = true; ["internal-server-error"] = true; ["item-not-found"] = true; ["jid-malformed"] = true; ["recipient-unavailable"] = true; ["redirect"] = true; ["remote-server-not-found"] = true; ["remote-server-timeout"] = true; ["service-unavailable"] = true; ["malformed error"] = true; }; local function get_error_condition(stanza) local _, condition = stanza:get_error(); return condition or "malformed error"; end local function is_kickable_error(stanza) local cond = get_error_condition(stanza); return kickable_error_conditions[cond] and cond; end local function getUsingPath(stanza, path, getText) local tag = stanza; for _, name in ipairs(path) do if type(tag) ~= 'table' then return; end tag = tag:child_with_name(name); end if tag and getText then tag = table.concat(tag); end return tag; end local function getTag(stanza, path) return getUsingPath(stanza, path); end local function getText(stanza, path) return getUsingPath(stanza, path, true); end ----------- 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"; elseif affiliation == "member" then return "participant"; elseif not affiliation then if not self:is_members_only() then return self:is_moderated() and "visitor" or "participant"; end end end function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up(); if code then stanza:tag("status", {code=code}):up(); end self:broadcast_except_nick(stanza, stanza.attr.from); local me = self._occupants[stanza.attr.from]; if me then stanza:tag("status", {code='110'}):up(); stanza.attr.to = sid; self:_route_stanza(stanza); end end function room_mt:broadcast_message(stanza, historic) local to = stanza.attr.to; for occupant, o_data in pairs(self._occupants) do for jid in pairs(o_data.sessions) do stanza.attr.to = jid; self:_route_stanza(stanza); end end stanza.attr.to = to; if historic then -- add to history local history = self._data['history']; if not history then history = {}; self._data['history'] = history; end stanza = st.clone(stanza); stanza.attr.to = ""; local stamp = datetime.datetime(); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) local entry = { stanza = stanza, stamp = stamp }; t_insert(history, entry); while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end end end function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then for jid in pairs(occupant.sessions) do stanza.attr.to = jid; self:_route_stanza(stanza); end end end end function room_mt:send_occupant_list(to) local current_nick = self._jid_nick[to]; for occupant, o_data in pairs(self._occupants) do if occupant ~= current_nick then local pres = get_filtered_presence(o_data.sessions[o_data.jid]); pres.attr.to, pres.attr.from = to, occupant; pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up(); self:_route_stanza(pres); end end end function room_mt:send_history(to, stanza) local history = self._data['history']; -- send discussion history if history then local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); local maxchars = history_tag and tonumber(history_tag.attr.maxchars); if maxchars then maxchars = math.floor(maxchars); end local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); if not history_tag then maxstanzas = 20; end local seconds = history_tag and tonumber(history_tag.attr.seconds); if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end local since = history_tag and history_tag.attr.since; if since then since = datetime.parse(since); since = since and datetime.datetime(since); end if seconds and (not since or since < seconds) then since = seconds; end local n = 0; local charcount = 0; for i=#history,1,-1 do local entry = history[i]; if maxchars then if not entry.chars then entry.stanza.attr.to = ""; entry.chars = #tostring(entry.stanza); end charcount = charcount + entry.chars + #to; if charcount > maxchars then break; end end if since and since > entry.stamp then break; end if n + 1 > maxstanzas then break; end n = n + 1; end for i=#history-n+1,#history do local msg = history[i].stanza; msg.attr.to = to; self:_route_stanza(msg); end end if self._data['subject'] then self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); end end function room_mt:get_disco_info(stanza) local count = 0; for _ in pairs(self._occupants) do count = count + 1; end return st.reply(stanza):query("http://jabber.org/protocol/disco#info") :tag("identity", {category="conference", type="text", name=self:get_name()}):up() :tag("feature", {var="http://jabber.org/protocol/muc"}):up() :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up() :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up() :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up() :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up() :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() :add_child(dataform.new({ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, { name = "muc#roominfo_description", label = "Description"}, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) ; end function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); for room_jid in pairs(self._occupants) do reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); end return reply; end function room_mt:set_subject(current_nick, subject) -- TODO check nick's authority if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; if self.save then self:save(); end local msg = st.message({type='groupchat', from=current_nick}) :tag('subject'):text(subject):up(); self:broadcast_message(msg, false); return true; end local function build_unavailable_presence_from_error(stanza) local type, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text then error_message = error_message..": "..text; end return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) :tag('status'):text(error_message); end function room_mt:set_name(name) if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end if self._data.name ~= name then self._data.name = name; if self.save then self:save(true); end end end function room_mt:get_name() return self._data.name or jid_split(self.jid); end function room_mt:set_description(description) if description == "" or type(description) ~= "string" then description = nil; end if self._data.description ~= description then self._data.description = description; if self.save then self:save(true); end end end function room_mt:get_description() return self._data.description; end function room_mt:set_password(password) if password == "" or type(password) ~= "string" then password = nil; end if self._data.password ~= password then self._data.password = password; if self.save then self:save(true); end end end function room_mt:get_password() return self._data.password; end function room_mt:set_moderated(moderated) moderated = moderated and true or nil; if self._data.moderated ~= moderated then self._data.moderated = moderated; if self.save then self:save(true); end end end function room_mt:is_moderated() return self._data.moderated; end function room_mt:set_members_only(members_only) members_only = members_only and true or nil; if self._data.members_only ~= members_only then self._data.members_only = members_only; if self.save then self:save(true); end end end function room_mt:is_members_only() return self._data.members_only; end function room_mt:set_persistent(persistent) persistent = persistent and true or nil; if self._data.persistent ~= persistent then self._data.persistent = persistent; if self.save then self:save(true); end end end function room_mt:is_persistent() return self._data.persistent; end function room_mt:set_hidden(hidden) hidden = hidden and true or nil; if self._data.hidden ~= hidden then self._data.hidden = hidden; if self.save then self:save(true); end end end function room_mt:is_hidden() return self._data.hidden; end function room_mt:set_changesubject(changesubject) changesubject = changesubject and true or nil; if self._data.changesubject ~= changesubject then self._data.changesubject = changesubject; if self.save then self:save(true); end end end function room_mt:get_changesubject() return self._data.changesubject; end function room_mt:get_historylength() return self._data.history_length or default_history_length; end function room_mt:set_historylength(length) length = math.min(tonumber(length) or default_history_length, max_history_length or math.huge); if length == default_history_length then length = nil; end self._data.history_length = length; end local function construct_stanza_id(room, stanza) local from_jid, to_nick = stanza.attr.from, stanza.attr.to; local from_nick = room._jid_nick[from_jid]; local occupant = room._occupants[to_nick]; local to_jid = occupant.jid; return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); end local function deconstruct_stanza_id(room, stanza) local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); local from_nick = room._jid_nick[from_jid]; if not(from_nick) then return; end if not(from_jid_possiblybare == from_jid or from_jid_possiblybare == jid_bare(from_jid)) then return; end local occupant = room._occupants[to_nick]; for to_jid in pairs(occupant and occupant.sessions or {}) do if md5(to_jid) == to_jid_hash then return from_nick, to_jid, id; end end end function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; local type = stanza.attr.type; log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end if stanza.name == "presence" then local pr = get_filtered_presence(stanza); pr.attr.from = current_nick; if type == "error" then -- error, kick em out! if current_nick then log("debug", "kicking %s from %s", current_nick, room); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); end elseif type == "unavailable" then -- unavailable if current_nick then log("debug", "%s leaving %s", current_nick, room); self._jid_nick[from] = nil; local occupant = self._occupants[current_nick]; local new_jid = next(occupant.sessions); if new_jid == from then new_jid = next(occupant.sessions, new_jid); end if new_jid then local jid = occupant.jid; occupant.jid = new_jid; occupant.sessions[from] = nil; pr.attr.to = from; pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() :tag("status", {code='110'}):up(); self:_route_stanza(pr); if jid ~= new_jid then pr = st.clone(occupant.sessions[new_jid]) :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); pr.attr.from = current_nick; self:broadcast_except_nick(pr, current_nick); end else occupant.role = 'none'; self:broadcast_presence(pr, from); self._occupants[current_nick] = nil; end end elseif not type then -- available if current_nick then --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence if current_nick == to then -- simple presence log("debug", "%s broadcasted presence", current_nick); self._occupants[current_nick].sessions[from] = pr; self:broadcast_presence(pr, from); else -- change nick local occupant = self._occupants[current_nick]; local is_multisession = next(occupant.sessions, next(occupant.sessions)); if self._occupants[to] or is_multisession then log("debug", "%s couldn't change nick", current_nick); local reply = st.error_reply(stanza, "cancel", "conflict"):up(); reply.tags[1].attr.code = "409"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); else local data = self._occupants[current_nick]; local to_nick = select(3, jid_split(to)); if to_nick then log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); local p = st.presence({type='unavailable', from=current_nick}); self:broadcast_presence(p, from, '303', to_nick); self._occupants[current_nick] = nil; self._occupants[to] = data; self._jid_nick[from] = to; pr.attr.from = to; self._occupants[to].sessions[from] = pr; self:broadcast_presence(pr, from); else --TODO malformed-jid end end end --else -- possible rejoin -- log("debug", "%s had connection replaced", current_nick); -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable -- self:handle_to_occupant(origin, stanza); -- resend available --end else -- enter room local new_nick = to; local is_merge; if self._occupants[to] then if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then new_nick = nil; end is_merge = true; end local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child("password", "http://jabber.org/protocol/muc"); password = password and password[1] ~= "" and password[1]; if self:get_password() and self:get_password() ~= password then log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); reply.tags[1].attr.code = "401"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); elseif not new_nick then log("debug", "%s couldn't join due to nick conflict: %s", from, to); local reply = st.error_reply(stanza, "cancel", "conflict"):up(); reply.tags[1].attr.code = "409"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); else log("debug", "%s joining as %s", from, to); if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; end local affiliation = self:get_affiliation(from); local role = self:get_default_role(affiliation) if role then -- new occupant if not is_merge then self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; else self._occupants[to].sessions[from] = get_filtered_presence(stanza); end self._jid_nick[from] = to; self:send_occupant_list(from); pr.attr.from = to; pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); if not is_merge then self:broadcast_except_nick(pr, to); end pr:tag("status", {code='110'}):up(); if self._data.whois == 'anyone' then pr:tag("status", {code='100'}):up(); end pr.attr.to = from; self:_route_stanza(pr); self:send_history(from, stanza); elseif not affiliation then -- registration required for entering members-only room local reply = st.error_reply(stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); else -- banned local reply = st.error_reply(stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); end end end elseif type ~= 'result' then -- bad type if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? end end elseif not current_nick then -- not in room if (type == "error" or type == "result") and stanza.name == "iq" then local id = stanza.attr.id; stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); if stanza.attr.id then self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; elseif type ~= "error" then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); end elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM origin.send(st.error_reply(stanza, "modify", "bad-request")); elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable else -- private stanza local o_data = self._occupants[to]; if o_data then log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); if stanza.name == "iq" then local id = stanza.attr.id; if stanza.attr.type == "get" or stanza.attr.type == "set" then stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); else stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); end if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end if stanza.attr.id then self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; else -- message stanza.attr.from = current_nick; for jid in pairs(o_data.sessions) do stanza.attr.to = jid; self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to = from, to; end elseif type ~= "error" and type ~= "result" then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); end end end function room_mt:send_form(origin, stanza) origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") :add_child(self:get_form_layout():form()) ); end function room_mt:get_form_layout() local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", { name = 'FORM_TYPE', type = 'hidden', value = 'http://jabber.org/protocol/muc#roomconfig' }, { name = 'muc#roomconfig_roomname', type = 'text-single', label = 'Name', value = self:get_name() or "", }, { name = 'muc#roomconfig_roomdesc', type = 'text-single', label = 'Description', value = self:get_description() or "", }, { name = 'muc#roomconfig_persistentroom', type = 'boolean', label = 'Make Room Persistent?', value = self:is_persistent() }, { name = 'muc#roomconfig_publicroom', type = 'boolean', label = 'Make Room Publicly Searchable?', value = not self:is_hidden() }, { name = 'muc#roomconfig_changesubject', type = 'boolean', label = 'Allow Occupants to Change Subject?', value = self:get_changesubject() }, { name = 'muc#roomconfig_whois', type = 'list-single', label = 'Who May Discover Real JIDs?', value = { { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' }, { value = 'anyone', label = 'Anyone', default = self._data.whois == 'anyone' } } }, { name = 'muc#roomconfig_roomsecret', type = 'text-private', label = 'Password', value = self:get_password() or "", }, { name = 'muc#roomconfig_moderatedroom', type = 'boolean', label = 'Make Room Moderated?', value = self:is_moderated() }, { name = 'muc#roomconfig_membersonly', type = 'boolean', label = 'Make Room Members-Only?', value = self:is_members_only() }, { name = 'muc#roomconfig_historylength', type = 'text-single', label = 'Maximum Number of History Messages Returned by Room', value = tostring(self:get_historylength()) } }); return module:fire_event("muc-config-form", { room = self, form = form }) or form; end local valid_whois = { moderators = true, anyone = true, } function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; local form; for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end local fields = self:get_form_layout():data(form); if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end local dirty = false local event = { room = self, fields = fields, changed = dirty }; module:fire_event("muc-config-submitted", event); dirty = event.changed or dirty; local name = fields['muc#roomconfig_roomname']; if name ~= self:get_name() then self:set_name(name); end local description = fields['muc#roomconfig_roomdesc']; if description ~= self:get_description() then self:set_description(description); end local persistent = fields['muc#roomconfig_persistentroom']; dirty = dirty or (self:is_persistent() ~= persistent) module:log("debug", "persistent=%s", tostring(persistent)); local moderated = fields['muc#roomconfig_moderatedroom']; dirty = dirty or (self:is_moderated() ~= moderated) module:log("debug", "moderated=%s", tostring(moderated)); local membersonly = fields['muc#roomconfig_membersonly']; dirty = dirty or (self:is_members_only() ~= membersonly) module:log("debug", "membersonly=%s", tostring(membersonly)); local public = fields['muc#roomconfig_publicroom']; dirty = dirty or (self:is_hidden() ~= (not public and true or nil)) local changesubject = fields['muc#roomconfig_changesubject']; dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil)) module:log('debug', 'changesubject=%s', changesubject and "true" or "false") local historylength = tonumber(fields['muc#roomconfig_historylength']); dirty = dirty or (historylength and (self:get_historylength() ~= historylength)); module:log('debug', 'historylength=%s', historylength) local whois = fields['muc#roomconfig_whois']; if not valid_whois[whois] then origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'")); return; end local whois_changed = self._data.whois ~= whois self._data.whois = whois module:log('debug', 'whois=%s', whois) local password = fields['muc#roomconfig_roomsecret']; if self:get_password() ~= password then self:set_password(password); end self:set_moderated(moderated); self:set_members_only(membersonly); self:set_persistent(persistent); self:set_hidden(not public); self:set_changesubject(changesubject); self:set_historylength(historylength); if self.save then self:save(true); end origin.send(st.reply(stanza)); if dirty or whois_changed then local msg = st.message({type='groupchat', from=self.jid}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() if dirty then msg.tags[1]:tag('status', {code = '104'}):up(); end if whois_changed then local code = (whois == 'moderators') and "173" or "172"; msg.tags[1]:tag('status', {code = code}):up(); end self:broadcast_message(msg, false) end end function room_mt:destroy(newjid, reason, password) local pr = st.presence({type = "unavailable"}) :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("item", { affiliation='none', role='none' }):up() :tag("destroy", {jid=newjid}) if reason then pr:tag("reason"):text(reason):up(); end if password then pr:tag("password"):text(password):up(); end for nick, occupant in pairs(self._occupants) do pr.attr.from = nick; for jid in pairs(occupant.sessions) do pr.attr.to = jid; self:_route_stanza(pr); self._jid_nick[jid] = nil; end self._occupants[nick] = nil; end self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); end function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if stanza.name == "iq" then if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_info(stanza)); elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_items(stanza)); elseif xmlns == "http://jabber.org/protocol/muc#admin" then local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); local current_nick = self._jid_nick[actor]; local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; if item and item.name == "item" then if type == "set" then local callback = function() origin.send(st.reply(stanza)); end if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); if not item.attr.jid then origin.send(st.error_reply(stanza, "modify", "jid-malformed")); return; end end if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation local occupant = self._occupants[self.jid.."/"..item.attr.nick]; if occupant then item.attr.jid = occupant.jid; end elseif not item.attr.nick and item.attr.jid then local nick = self._jid_nick[item.attr.jid]; if nick then item.attr.nick = select(3, jid_split(nick)); end end local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; if item.attr.affiliation and item.attr.jid and not item.attr.role then local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); if not success then origin.send(st.error_reply(stanza, errtype, err)); end elseif item.attr.role and item.attr.nick and not item.attr.affiliation then local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); if not success then origin.send(st.error_reply(stanza, errtype, err)); end else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end elseif type == "get" then local _aff = item.attr.affiliation; local _rol = item.attr.role; if _aff and not _rol then if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for jid, affiliation in pairs(self._affiliations) do if affiliation == _aff then reply:tag("item", {affiliation = _aff, jid = jid}):up(); end end origin.send(reply); else origin.send(st.error_reply(stanza, "auth", "forbidden")); end elseif _rol and not _aff then if role == "moderator" then -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for occupant_jid, occupant in pairs(self._occupants) do if occupant.role == _rol then reply:tag("item", { nick = select(3, jid_split(occupant_jid)), role = _rol or "none", affiliation = occupant.affiliation or "none", jid = occupant.jid }):up(); end end origin.send(reply); else origin.send(st.error_reply(stanza, "auth", "forbidden")); end else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); elseif stanza.attr.type == "get" then self:send_form(origin, stanza); elseif stanza.attr.type == "set" then local child = stanza.tags[1].tags[1]; if not child then origin.send(st.error_reply(stanza, "modify", "bad-request")); elseif child.name == "destroy" then local newjid = child.attr.jid; local reason, password; for _,tag in ipairs(child.tags) do if tag.name == "reason" then reason = #tag.tags == 0 and tag[1]; elseif tag.name == "password" then password = #tag.tags == 0 and tag[1]; end end self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); else self:process_form(origin, stanza); end end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end elseif stanza.name == "message" and type == "groupchat" then local from, to = stanza.attr.from, stanza.attr.to; local current_nick = self._jid_nick[from]; local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); else local from = stanza.attr.from; stanza.attr.from = current_nick; local subject = getText(stanza, {"subject"}); if subject then if occupant.role == "moderator" or ( self._data.changesubject and occupant.role == "participant" ) then -- and participant self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza else stanza.attr.from = from; origin.send(st.error_reply(stanza, "auth", "forbidden")); end else self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end stanza.attr.from = from; end elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick local to = stanza.attr.to; local current_nick = self._jid_nick[stanza.attr.from]; if current_nick then stanza.attr.to = current_nick; self:handle_to_occupant(origin, stanza); stanza.attr.to = to; elseif type ~= "error" and type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; local payload = (#x.tags == 1 and x.tags[1]); if payload and payload.name == "invite" and payload.attr.to then local _from, _to = stanza.attr.from, stanza.attr.to; local _invitee = jid_prep(payload.attr.to); if _invitee then local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); if self:get_password() then invite:tag("password"):text(self:get_password()):up(); end invite:up() :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this :text(_reason or "") :up() :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); if self:is_members_only() and not self:get_affiliation(_invitee) then log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end self:_route_stanza(invite); else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); end else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end else if type == "error" or type == "result" then return; end origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end end function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); if to_resource then self:handle_to_occupant(origin, stanza); else self:handle_to_room(origin, stanza); end end function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end function room_mt:get_affiliation(jid) local node, host, resource = jid_split(jid); local bare = node and node.."@"..host or host; local result = self._affiliations[bare]; -- Affiliations are granted, revoked, and maintained based on the user's bare JID. if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) jid = jid_bare(jid); if affiliation == "none" then affiliation = nil; end if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then return nil, "modify", "not-acceptable"; end if actor ~= true then local actor_affiliation = self:get_affiliation(actor); local target_affiliation = self:get_affiliation(jid); if target_affiliation == affiliation then -- no change, shortcut if callback then callback(); end return true; end if actor_affiliation ~= "owner" then if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then return nil, "cancel", "not-allowed"; end elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change local is_last = true; for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end if is_last then return nil, "cancel", "conflict"; end end end self._affiliations[jid] = affiliation; local role = self:get_default_role(affiliation); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("item", {affiliation=affiliation or "none", role=role or "none"}) :tag("reason"):text(reason or ""):up() :up(); local presence_type = nil; if not role then -- getting kicked presence_type = "unavailable"; if affiliation == "outcast" then x:tag("status", {code="301"}):up(); -- banned else x:tag("status", {code="321"}):up(); -- affiliation change end end local modified_nicks = {}; for nick, occupant in pairs(self._occupants) do if jid_bare(occupant.jid) == jid then if not role then -- getting kicked self._occupants[nick] = nil; else occupant.affiliation, occupant.role = affiliation, role; end for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick if not role then self._jid_nick[jid] = nil; end local p = st.clone(pres); p.attr.from = nick; p.attr.type = presence_type; p.attr.to = jid; p:add_child(x); self:_route_stanza(p); if occupant.jid == jid then modified_nicks[nick] = p; end end end end if self.save then self:save(); end if callback then callback(); end for nick,p in pairs(modified_nicks) do p.attr.from = nick; self:broadcast_except_nick(p, nick); end return true; end function room_mt:get_role(nick) local session = self._occupants[nick]; return session and session.role or nil; end function room_mt:can_set_role(actor_jid, occupant_jid, role) local occupant = self._occupants[occupant_jid]; if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end if actor_jid == true then return true; end local actor = self._occupants[self._jid_nick[actor_jid]]; if actor.role == "moderator" then if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then if actor.affiliation == "owner" or actor.affiliation == "admin" then return true; elseif occupant.role ~= "moderator" and role ~= "moderator" then return true; end end end return nil, "cancel", "not-allowed"; end function room_mt:set_role(actor, occupant_jid, role, callback, reason) if role == "none" then role = nil; end if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); if not allowed then return allowed, err_type, err_condition; end local occupant = self._occupants[occupant_jid]; local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) :tag("reason"):text(reason or ""):up() :up(); local presence_type = nil; if not role then -- kick presence_type = "unavailable"; self._occupants[occupant_jid] = nil; for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick self._jid_nick[jid] = nil; end x:tag("status", {code = "307"}):up(); else occupant.role = role; end local bp; for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick local p = st.clone(pres); p.attr.from = occupant_jid; p.attr.type = presence_type; p.attr.to = jid; p:add_child(x); self:_route_stanza(p); if occupant.jid == jid then bp = p; end end if callback then callback(); end if bp then self:broadcast_except_nick(bp, occupant_jid); end return true; end function room_mt:_route_stanza(stanza) local muc_child; local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; local from_occupant = self._occupants[stanza.attr.from]; if stanza.name == "presence" then if to_occupant and from_occupant then if self._data.whois == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); else if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); end end end end if muc_child then for _, item in pairs(muc_child.tags) do if item.name == "item" then if from_occupant == to_occupant then item.attr.jid = stanza.attr.to; else item.attr.jid = from_occupant.jid; end end end end self:route_stanza(stanza); if muc_child then for _, item in pairs(muc_child.tags) do if item.name == "item" then item.attr.jid = nil; end end end end local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; _jid_nick = {}; _occupants = {}; _data = { whois = 'moderators'; history_length = math.min((config and config.history_length) or default_history_length, max_history_length); }; _affiliations = {}; }, room_mt); end function _M.set_max_history_length(_max_history_length) max_history_length = _max_history_length or math.huge; end _M.room_mt = room_mt; return _M; prosody-0.9.1/plugins/muc/mod_muc.lua0000644000175000017500000001762212213321667017532 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end local muc_host = module:get_host(); local muc_name = module:get_option("name"); if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation then if restrict_room_creation == true then restrict_room_creation = "admin"; elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then restrict_room_creation = nil; end end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; 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 um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; rooms = {}; local rooms = rooms; 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")); local function is_admin(jid) return um_is_admin(jid, module.host); end local _set_affiliation = muc_new_room.room_mt.set_affiliation; local _get_affiliation = muc_new_room.room_mt.get_affiliation; function muclib.room_mt:get_affiliation(jid) if is_admin(jid) then return "owner"; end return _get_affiliation(self, jid); end function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reason) if is_admin(jid) then return nil, "modify", "not-acceptable"; end return _set_affiliation(self, actor, jid, affiliation, callback, reason); end local function room_route_stanza(room, stanza) module:send(stanza); end local function room_save(room, forced) local node = jid_split(room.jid); persistent_rooms[room.jid] = room._data.persistent; if room._data.persistent then local history = room._data.history; room._data.history = nil; local data = { jid = room.jid; _data = room._data; _affiliations = room._affiliations; }; room_configs:set(node, data); room._data.history = history; elseif forced then room_configs:set(node, nil); if not next(room._occupants) then -- Room empty rooms[room.jid] = nil; end end if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end function create_room(jid) local room = muc_new_room(jid); room.route_stanza = room_route_stanza; room.save = room_save; rooms[jid] = room; module:fire_event("muc-room-created", { room = room }); return room; end local persistent_errors = false; for jid in pairs(persistent_rooms) do local node = jid_split(jid); local data = room_configs:get(node); if data then local room = create_room(jid); room._data = data._data; room._affiliations = data._affiliations; else -- missing room data persistent_rooms[jid] = nil; module:log("error", "Missing data for room '%s', removing from persistent room list", jid); persistent_errors = true; end 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; host_room.save = room_save; local function get_disco_info(stanza) return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") :tag("identity", {category='conference', type='text', name=muc_name}):up() :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply end local function get_disco_items(stanza) local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); for jid, room in pairs(rooms) do if not room:is_hidden() then reply:tag("item", {jid=jid, name=room:get_name()}):up(); end end return reply; -- TODO cache disco reply end local function handle_to_domain(event) local origin, stanza = event.origin, event.stanza; local type = stanza.attr.type; if type == "error" or type == "result" then return; end if stanza.name == "iq" and type == "get" then local xmlns = stanza.tags[1].attr.xmlns; local node = stanza.tags[1].attr.node; if xmlns == "http://jabber.org/protocol/disco#info" and not node then origin.send(get_disco_info(stanza)); elseif xmlns == "http://jabber.org/protocol/disco#items" and not node then origin.send(get_disco_items(stanza)); elseif xmlns == "http://jabber.org/protocol/muc#unique" then origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc end else host_room:handle_stanza(origin, stanza); --origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); end return true; end function stanza_handler(event) local origin, stanza = event.origin, event.stanza; local bare = jid_bare(stanza.attr.to); local room = rooms[bare]; if not room then if stanza.name ~= "presence" then origin.send(st.error_reply(stanza, "cancel", "item-not-found")); return true; end if not(restrict_room_creation) or (restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then room = create_room(bare); end end if room then room:handle_stanza(origin, stanza); if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room rooms[bare] = nil; -- discard room end else origin.send(st.error_reply(stanza, "cancel", "not-allowed")); end return true; end module:hook("iq/bare", stanza_handler, -1); module:hook("message/bare", stanza_handler, -1); module:hook("presence/bare", stanza_handler, -1); module:hook("iq/full", stanza_handler, -1); module:hook("message/full", stanza_handler, -1); module:hook("presence/full", stanza_handler, -1); module:hook("iq/host", handle_to_domain, -1); module:hook("message/host", handle_to_domain, -1); module:hook("presence/host", handle_to_domain, -1); hosts[module.host].send = function(stanza) -- FIXME do a generic fix if stanza.attr.type == "result" or stanza.attr.type == "error" then module:send(stanza); else error("component.send only supports result and error stanzas at the moment"); end end hosts[module:get_host()].muc = { rooms = rooms }; local saved = false; module.save = function() saved = true; return {rooms = rooms}; end module.restore = function(data) for jid, oldroom in pairs(data.rooms or {}) do local room = create_room(jid); room._jid_nick = oldroom._jid_nick; room._occupants = oldroom._occupants; room._data = oldroom._data; room._affiliations = oldroom._affiliations; end hosts[module:get_host()].muc = { rooms = rooms }; end function shutdown_room(room, stanza) for nick, occupant in pairs(room._occupants) do stanza.attr.from = nick; for jid in pairs(occupant.sessions) do stanza.attr.to = jid; room:_route_stanza(stanza); room._jid_nick[jid] = nil; end room._occupants[nick] = nil; end end function shutdown_component() if not saved then local stanza = st.presence({type = "unavailable"}) :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("item", { affiliation='none', role='none' }):up(); for roomjid, room in pairs(rooms) do shutdown_room(room, stanza); end shutdown_room(host_room, stanza); end end module.unload = shutdown_component; module:hook_global("server-stopping", shutdown_component); prosody-0.9.1/plugins/storage/0000775000175000017500000000000012213321667016256 5ustar matthewmatthewprosody-0.9.1/plugins/storage/xep227store.lib.lua0000644000175000017500000001041612213321667021632 0ustar matthewmatthew local st = require "util.stanza"; local function getXml(user, host) local jid = user.."@"..host; local path = "data/"..jid..".xml"; local f = io.open(path); if not f then return; end local s = f:read("*a"); return parse_xml_real(s); end local function setXml(user, host, xml) local jid = user.."@"..host; local path = "data/"..jid..".xml"; if xml then local f = io.open(path, "w"); if not f then return; end local s = tostring(xml); f:write(s); f:close(); return true; else return os.remove(path); end end local function getUserElement(xml) if xml and xml.name == "server-data" then local host = xml.tags[1]; if host and host.name == "host" then local user = host.tags[1]; if user and user.name == "user" then return user; end end end end local function createOuterXml(user, host) return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'}) :tag("host", {jid=host}) :tag("user", {name = user}); end local function removeFromArray(array, value) for i,item in ipairs(array) do if item == value then table.remove(array, i); return; end end end local function removeStanzaChild(s, child) removeFromArray(s.tags, child); removeFromArray(s, child); end local handlers = {}; handlers.accounts = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user and user.attr.password then return { password = user.attr.password }; end end; set = function(self, user, data) if data and data.password then local xml = getXml(user, self.host); if not xml then xml = createOuterXml(user, self.host); end local usere = getUserElement(xml); usere.attr.password = data.password; return setXml(user, self.host, xml); else return setXml(user, self.host, nil); end end; }; handlers.vcard = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user then local vcard = user:get_child("vCard", 'vcard-temp'); if vcard then return st.preserialize(vcard); end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local vcard = usere:get_child("vCard", 'vcard-temp'); if vcard then removeStanzaChild(usere, vcard); elseif not data then return true; end if data then vcard = st.deserialize(data); usere:add_child(vcard); end return setXml(user, self.host, xml); end return true; end; }; handlers.private = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user then local private = user:get_child("query", "jabber:iq:private"); if private then local r = {}; for _, tag in ipairs(private.tags) do r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag); end return r; end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local private = usere:get_child("query", 'jabber:iq:private'); if private then removeStanzaChild(usere, private); end if data and next(data) ~= nil then private = st.stanza("query", {xmlns='jabber:iq:private'}); for _,tag in pairs(data) do private:add_child(st.deserialize(tag)); end usere:add_child(private); end return setXml(user, self.host, xml); end return true; end; }; ----------------------------- local driver = {}; driver.__index = driver; function driver:open(host, datastore, typ) local cache_key = host.." "..datastore; if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end local instance = setmetatable({}, self); instance.host = host; instance.datastore = datastore; local handler = handlers[datastore]; if not handler then return nil; end for key,val in pairs(handler) do instance[key] = val; end if instance.init then instance:init(); end self.ds_cache[cache_key] = instance; return instance; end ----------------------------- local _M = {}; function _M.new() local instance = setmetatable({}, driver); instance.__index = instance; instance.ds_cache = {}; return instance; end return _M; prosody-0.9.1/plugins/storage/sqlbasic.lib.lua0000644000175000017500000000574212213321667021335 0ustar matthewmatthew -- Basic SQL driver -- This driver stores data as simple key-values local ser = require "util.serialization".serialize; local envload = require "util.envload".envload; local deser = function(data) module:log("debug", "deser: %s", tostring(data)); if not data then return nil; end local f = envload("return "..data, nil, {}); if not f then return nil; end local s, d = pcall(f); if not s then return nil; end return d; end; local driver = {}; driver.__index = driver; driver.item_table = "item"; driver.list_table = "list"; function driver:prepare(sql) module:log("debug", "query: %s", sql); local err; if not self.sqlcache then self.sqlcache = {}; end local r = self.sqlcache[sql]; if r then return r; end r, err = self.connection:prepare(sql); if not r then error("Unable to prepare SQL statement: "..err); end self.sqlcache[sql] = r; return r; end function driver:load(username, host, datastore) local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?"); select:execute(username, host, datastore); local row = select:fetch(); return row and deser(row[1]) or nil; end function driver:store(username, host, datastore, data) if not data or next(data) == nil then local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?"); delete:execute(username, host, datastore); return true; else local d = self:load(username, host, datastore); if d then -- update local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?"); return update:execute(ser(data), username, host, datastore); else -- insert local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)"); return insert:execute(username, host, datastore, ser(data)); end end end function driver:list_append(username, host, datastore, data) if not data then return; end local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)"); return insert:execute(username, host, datastore, ser(data)); end function driver:list_store(username, host, datastore, data) -- remove existing data local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?"); delete:execute(username, host, datastore); if data and next(data) ~= nil then -- add data for _, d in ipairs(data) do self:list_append(username, host, datastore, ser(d)); end end return true; end function driver:list_load(username, host, datastore) local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?"); select:execute(username, host, datastore); local r = {}; for row in select:rows() do table.insert(r, deser(row[1])); end return r; end local _M = {}; function _M.new(dbtype, dbname, ...) local d = {}; setmetatable(d, driver); local dbh = get_database(dbtype, dbname, ...); --d:set_connection(dbh); d.connection = dbh; return d; end return _M; prosody-0.9.1/plugins/storage/mod_xep0227.lua0000644000175000017500000001001712213321667020724 0ustar matthewmatthew local ipairs, pairs = ipairs, pairs; local setmetatable = setmetatable; local tostring = tostring; local next = next; local t_remove = table.remove; local os_remove = os.remove; local io_open = io.open; local st = require "util.stanza"; local parse_xml_real = require "util.xml".parse; local function getXml(user, host) local jid = user.."@"..host; local path = "data/"..jid..".xml"; local f = io_open(path); if not f then return; end local s = f:read("*a"); return parse_xml_real(s); end local function setXml(user, host, xml) local jid = user.."@"..host; local path = "data/"..jid..".xml"; if xml then local f = io_open(path, "w"); if not f then return; end local s = tostring(xml); f:write(s); f:close(); return true; else return os_remove(path); end end local function getUserElement(xml) if xml and xml.name == "server-data" then local host = xml.tags[1]; if host and host.name == "host" then local user = host.tags[1]; if user and user.name == "user" then return user; end end end end local function createOuterXml(user, host) return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'}) :tag("host", {jid=host}) :tag("user", {name = user}); end local function removeFromArray(array, value) for i,item in ipairs(array) do if item == value then t_remove(array, i); return; end end end local function removeStanzaChild(s, child) removeFromArray(s.tags, child); removeFromArray(s, child); end local handlers = {}; handlers.accounts = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user and user.attr.password then return { password = user.attr.password }; end end; set = function(self, user, data) if data and data.password then local xml = getXml(user, self.host); if not xml then xml = createOuterXml(user, self.host); end local usere = getUserElement(xml); usere.attr.password = data.password; return setXml(user, self.host, xml); else return setXml(user, self.host, nil); end end; }; handlers.vcard = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user then local vcard = user:get_child("vCard", 'vcard-temp'); if vcard then return st.preserialize(vcard); end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local vcard = usere:get_child("vCard", 'vcard-temp'); if vcard then removeStanzaChild(usere, vcard); elseif not data then return true; end if data then vcard = st.deserialize(data); usere:add_child(vcard); end return setXml(user, self.host, xml); end return true; end; }; handlers.private = { get = function(self, user) local user = getUserElement(getXml(user, self.host)); if user then local private = user:get_child("query", "jabber:iq:private"); if private then local r = {}; for _, tag in ipairs(private.tags) do r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag); end return r; end end end; set = function(self, user, data) local xml = getXml(user, self.host); local usere = xml and getUserElement(xml); if usere then local private = usere:get_child("query", 'jabber:iq:private'); if private then removeStanzaChild(usere, private); end if data and next(data) ~= nil then private = st.stanza("query", {xmlns='jabber:iq:private'}); for _,tag in pairs(data) do private:add_child(st.deserialize(tag)); end usere:add_child(private); end return setXml(user, self.host, xml); end return true; end; }; ----------------------------- local driver = {}; function driver:open(host, datastore, typ) local instance = setmetatable({}, self); instance.host = host; instance.datastore = datastore; local handler = handlers[datastore]; if not handler then return nil; end for key,val in pairs(handler) do instance[key] = val; end if instance.init then instance:init(); end return instance; end module:provides("storage", driver); prosody-0.9.1/plugins/mod_c2s.lua0000644000175000017500000002227712213321667016653 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module:set_global(); local add_task = require "util.timer".add_task; local new_xmpp_stream = require "util.xmppstream".new; local nameprep = require "util.encodings".stringprep.nameprep; local sessionmanager = require "core.sessionmanager"; local st = require "util.stanza"; local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; local uuid_generate = require "util.uuid".generate; local xpcall, tostring, type = xpcall, tostring, type; local traceback = debug.traceback; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; local log = module._log; local c2s_timeout = module:get_option_number("c2s_timeout"); local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5); local opt_keepalives = module:get_option_boolean("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); local sessions = module:shared("sessions"); local core_process_stanza = prosody.core_process_stanza; local hosts = prosody.hosts; local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza }; local listener = {}; --- Stream events handlers local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; function stream_callbacks.streamopened(session, attr) local send = session.send; session.host = nameprep(attr.to); if not session.host then session:close{ condition = "improper-addressing", text = "A valid 'to' attribute is required on stream headers" }; return; end session.version = tonumber(attr.version) or 0; session.streamid = uuid_generate(); (session.log or session)("debug", "Client sent opening to %s", session.host); if not hosts[session.host] then -- We don't serve this host... session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; return; end send(""..st.stanza("stream:stream", { xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams'; id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag()); (session.log or log)("debug", "Sent reply to client"); session.notopen = nil; -- If session.secure is *false* (not nil) then it means we /were/ encrypting -- since we now have a new stream header, session is secured if session.secure == false then session.secure = true; -- Check if TLS compression is used local sock = session.conn:socket(); if sock.info then session.compressed = sock:info"compression"; elseif sock.compression then session.compressed = sock:compression(); --COMPAT mw/luasec-hg end end local features = st.stanza("stream:features"); hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); module:fire_event("stream-features", session, features); send(features); end function stream_callbacks.streamclosed(session) session.log("debug", "Received "); session:close(false); end function stream_callbacks.error(session, error, data) if error == "no-stream" then session.log("debug", "Invalid opening stream header"); session:close("invalid-namespace"); elseif error == "parse-error" then (session.log or log)("debug", "Client XML parse error: %s", tostring(data)); session:close("not-well-formed"); elseif error == "stream-error" then local condition, text = "undefined-condition"; for child in data:children() do if child.attr.xmlns == xmlns_xmpp_streams then if child.name ~= "text" then condition = child.name; else text = child:get_text(); end if condition ~= "undefined-condition" and text then break; end end end text = condition .. (text and (" ("..text..")") or ""); session.log("info", "Session closed by remote with error: %s", text); session:close(nil, text); end 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 return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); end end --- Session methods local function session_close(session, reason) local log = session.log or log; if session.conn then if session.notopen then session.send(""); 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 stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif type(reason) == "table" then if reason.condition then stream_error:tag(reason.condition, stream_xmlns_attr):up(); if reason.text then stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then stream_error:add_child(reason.extra); end elseif reason.name then -- a stanza 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(""); function session.send() return false; end local reason = (reason and (reason.name or reason.text or reason.condition)) or reason; session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed"); -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; if reason == nil and not session.notopen and session.type == "c2s" then -- Grace time to process data from authenticated cleanly-closed stream add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); sm_destroy_session(session, reason); conn:close(); end end); else sm_destroy_session(session, reason); conn:close(); end end end module:hook_global("user-deleted", function(event) local username, host = event.username, event.host; local user = hosts[host].sessions[username]; if user and user.sessions then for jid, session in pairs(user.sessions) do session:close{ condition = "not-authorized", text = "Account deleted" }; end end end, 200); --- Port listener function listener.onconnect(conn) local session = sm_new_session(conn); sessions[conn] = session; session.log("info", "Client connected"); -- Client is using legacy SSL (otherwise mod_tls sets this flag) if conn:ssl() then session.secure = true; -- Check if TLS compression is used local sock = conn:socket(); if sock.info then session.compressed = sock:info"compression"; elseif sock.compression then session.compressed = sock:compression(); --COMPAT mw/luasec-hg end end if opt_keepalives then conn:setoption("keepalive", opt_keepalives); end session.close = session_close; local stream = new_xmpp_stream(session, stream_callbacks); session.stream = stream; session.notopen = true; function session.reset_stream() session.notopen = true; session.stream:reset(); end local filter = session.filter; function session.data(data) data = filter("bytes/in", data); if data then local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); session:close("not-well-formed"); end end if c2s_timeout then add_task(c2s_timeout, function () if session.type == "c2s_unauthed" then session:close("connection-timeout"); end end); end session.dispatch_stanza = stream_callbacks.handlestanza; end function listener.onincoming(conn, data) local session = sessions[conn]; if session then session.data(data); end end function listener.ondisconnect(conn, err) local session = sessions[conn]; if session then (session.log or log)("info", "Client disconnected: %s", err or "connection closed"); sm_destroy_session(session, err); sessions[conn] = nil; end end function listener.associate_session(conn, session) sessions[conn] = session; end module:hook("server-stopping", function(event) local reason = event.reason; for _, session in pairs(sessions) do session:close{ condition = "system-shutdown", text = reason }; end end, 1000); module:provides("net", { name = "c2s"; listener = listener; default_port = 5222; encryption = "starttls"; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); module:provides("net", { name = "legacy_ssl"; listener = listener; encryption = "ssl"; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); prosody-0.9.1/plugins/mod_watchregistrations.lua0000644000175000017500000000201412213321667022073 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local host = module:get_host(); local jid_prep = require "util.jid".prep; local registration_watchers = module:get_option_set("registration_watchers", module:get_option("admins", {})) / jid_prep; local registration_notification = module:get_option("registration_notification", "User $username just registered on $host from $ip"); local st = require "util.stanza"; module:hook("user-registered", function (user) module:log("debug", "Notifying of new registration"); local message = st.message{ type = "chat", from = host } :tag("body") :text(registration_notification:gsub("%$(%w+)", function (v) return user[v] or user.session and user.session[v] or nil; end)); for jid in registration_watchers do module:log("debug", "Notifying %s", jid); message.attr.to = jid; module:send(message); end end); prosody-0.9.1/man/0000775000175000017500000000000012213321667013704 5ustar matthewmatthewprosody-0.9.1/man/prosodyctl.man0000644000175000017500000000562012213321667016604 0ustar matthewmatthew.TH PROSODYCTL 1 "2009-07-02" .SH NAME prosodyctl \- Manage a Prosody XMPP server .SH SYNOPSIS \fBprosodyctl\fP \fIcommand\fP [\fI--help\fP] .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 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. .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. .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" .SH OPTIONS .IP \fI--help\fP Display help text for the specified command. .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. .SH ONLINE More information may be found online at: \fIhttp://prosody.im/\fP .SH AUTHORS Dwayne Bent prosody-0.9.1/util/0000775000175000017500000000000012213321667014106 5ustar matthewmatthewprosody-0.9.1/util/dataforms.lua0000644000175000017500000001427112213321667016574 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local setmetatable = setmetatable; local pairs, ipairs = pairs, ipairs; local tostring, type, next = tostring, type, next; local t_concat = table.concat; local st = require "util.stanza"; local jid_prep = require "util.jid".prep; module "dataforms" local xmlns_forms = 'jabber:x:data'; local form_t = {}; local form_mt = { __index = form_t }; function new(layout) return setmetatable(layout, form_mt); end function form_t.form(layout, data, formtype) local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" }); if layout.title then form:tag("title"):text(layout.title):up(); end if layout.instructions then form:tag("instructions"):text(layout.instructions):up(); end for n, field in ipairs(layout) do local field_type = field.type or "text-single"; -- Add field tag form:tag("field", { type = field_type, var = field.name, label = field.label }); local value = (data and data[field.name]) or field.value; if value then -- Add value, depending on type if field_type == "hidden" then if type(value) == "table" then -- Assume an XML snippet form:tag("value") :add_child(value) :up(); else form:tag("value"):text(tostring(value)):up(); end elseif field_type == "boolean" then form:tag("value"):text((value and "1") or "0"):up(); elseif field_type == "fixed" then form:tag("value"):text(value):up(); elseif field_type == "jid-multi" then for _, jid in ipairs(value) do form:tag("value"):text(jid):up(); end elseif field_type == "jid-single" then form:tag("value"):text(value):up(); elseif field_type == "text-single" or field_type == "text-private" then form:tag("value"):text(value):up(); elseif field_type == "text-multi" then -- Split into multiple tags, one for each line for line in value:gmatch("([^\r\n]+)\r?\n*") do form:tag("value"):text(line):up(); end elseif field_type == "list-single" then local has_default = false; for _, val in ipairs(value) do if type(val) == "table" then form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); if val.default and (not has_default) then form:tag("value"):text(val.value):up(); has_default = true; end else form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up(); end end elseif field_type == "list-multi" then for _, val in ipairs(value) do if type(val) == "table" then form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); if val.default then form:tag("value"):text(val.value):up(); end else form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up(); end end end end if field.required then form:tag("required"):up(); end -- Jump back up to list of fields form:up(); end return form; end local field_readers = {}; function form_t.data(layout, stanza) local data = {}; local errors = {}; for _, field in ipairs(layout) do local tag; for field_tag in stanza:childtags() do if field.name == field_tag.attr.var then tag = field_tag; break; end end if not tag then if field.required then errors[field.name] = "Required value missing"; end else local reader = field_readers[field.type]; if reader then data[field.name], errors[field.name] = reader(tag, field.required); end end end if next(errors) then return data, errors; end return data; end field_readers["text-single"] = function (field_tag, required) local data = field_tag:get_child_text("value"); if data and #data > 0 then return data elseif required then return nil, "Required value missing"; end end field_readers["text-private"] = field_readers["text-single"]; field_readers["jid-single"] = function (field_tag, required) local raw_data = field_tag:get_child_text("value") local data = jid_prep(raw_data); if data and #data > 0 then return data elseif raw_data then return nil, "Invalid JID: " .. raw_data; elseif required then return nil, "Required value missing"; end end field_readers["jid-multi"] = function (field_tag, required) local result = {}; local err = {}; for value_tag in field_tag:childtags("value") do local raw_value = value_tag:get_text(); local value = jid_prep(raw_value); result[#result+1] = value; if raw_value and not value then err[#err+1] = ("Invalid JID: " .. raw_value); end end if #result > 0 then return result, (#err > 0 and t_concat(err, "\n") or nil); elseif required then return nil, "Required value missing"; end end field_readers["list-multi"] = function (field_tag, required) local result = {}; for value in field_tag:childtags("value") do result[#result+1] = value:get_text(); end if #result > 0 then return result; elseif required then return nil, "Required value missing"; end end field_readers["text-multi"] = function (field_tag, required) local data, err = field_readers["list-multi"](field_tag, required); if data then data = t_concat(data, "\n"); end return data, err; end field_readers["list-single"] = field_readers["text-single"]; local boolean_values = { ["1"] = true, ["true"] = true, ["0"] = false, ["false"] = false, }; field_readers["boolean"] = function (field_tag, required) local raw_value = field_tag:get_child_text("value"); local value = boolean_values[raw_value ~= nil and raw_value]; if value ~= nil then return value; elseif raw_value then return nil, "Invalid boolean representation"; elseif required then return nil, "Required value missing"; end end field_readers["hidden"] = function (field_tag) return field_tag:get_child_text("value"); end return _M; --[=[ Layout: { title = "MUC Configuration", instructions = [[Use this form to configure options for this MUC room.]], { name = "FORM_TYPE", type = "hidden", required = true }; { name = "field-name", type = "field-type", required = false }; } --]=] prosody-0.9.1/util/adhoc.lua0000644000175000017500000000172612213321667015673 0ustar matthewmatthewlocal 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 }; prosody-0.9.1/util/sasl.lua0000644000175000017500000000750212213321667015555 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local pairs, ipairs = pairs, ipairs; local t_insert = table.insert; local type = type local setmetatable = setmetatable; local assert = assert; local require = require; module "sasl" --[[ Authentication Backend Prototypes: state = false : disabled state = true : enabled state = nil : non-existant ]] local method = {}; method.__index = method; local mechanisms = {}; local backend_mechanism = {}; -- register a new SASL mechanims function registerMechanism(name, backends, f) assert(type(name) == "string", "Parameter name MUST be a string."); assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table."); assert(type(f) == "function", "Parameter f MUST be a function."); mechanisms[name] = f for _, backend_name in ipairs(backends) do if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end t_insert(backend_mechanism[backend_name], name); end end -- create a new SASL object which can be used to authenticate clients function new(realm, profile) local mechanisms = profile.mechanisms; if not mechanisms then mechanisms = {}; for backend, f in pairs(profile) do if backend_mechanism[backend] then for _, mechanism in ipairs(backend_mechanism[backend]) do mechanisms[mechanism] = true; end end end profile.mechanisms = mechanisms; end return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method); end -- get a fresh clone with the same realm and profile function method:clean_clone() return new(self.realm, self.profile) end -- get a list of possible SASL mechanims to use function method:mechanisms() return self.mechs; end -- select a mechanism to use function method:select(mechanism) if not self.selected and self.mechs[mechanism] then self.selected = mechanism; return true; end end -- feed new messages to process into the library function method:process(message) --if message == "" or message == nil then return "failure", "malformed-request" end return mechanisms[self.selected](self, message); end -- load the mechanisms require "util.sasl.plain" .init(registerMechanism); require "util.sasl.digest-md5".init(registerMechanism); require "util.sasl.anonymous" .init(registerMechanism); require "util.sasl.scram" .init(registerMechanism); return _M; prosody-0.9.1/util/caps.lua0000644000175000017500000000401712213321667015537 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local base64 = require "util.encodings".base64.encode; local sha1 = require "util.hashes".sha1; local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat; local ipairs = ipairs; module "caps" function calculate_hash(disco_info) local identities, features, extensions = {}, {}, {}; for _, tag in ipairs(disco_info) do if tag.name == "identity" then t_insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or "")); elseif tag.name == "feature" then t_insert(features, tag.attr.var or ""); elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then local form = {}; local FORM_TYPE; for _, field in ipairs(tag.tags) do if field.name == "field" and field.attr.var then local values = {}; for _, val in ipairs(field.tags) do val = #val.tags == 0 and val:get_text(); if val then t_insert(values, val); end end t_sort(values); if field.attr.var == "FORM_TYPE" then FORM_TYPE = values[1]; elseif #values > 0 then t_insert(form, field.attr.var.."\0"..t_concat(values, "<")); else t_insert(form, field.attr.var); end end end t_sort(form); form = t_concat(form, "<"); if FORM_TYPE then form = FORM_TYPE.."\0"..form; end t_insert(extensions, form); end end t_sort(identities); t_sort(features); t_sort(extensions); if #identities > 0 then identities = t_concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end if #features > 0 then features = t_concat(features, "<").."<"; else features = ""; end if #extensions > 0 then extensions = t_concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end local S = identities..features..extensions; local ver = base64(sha1(S)); return ver, S; end return _M; prosody-0.9.1/util/debug.lua0000644000175000017500000001337712213321667015710 0ustar matthewmatthew-- Variables ending with these names will not -- have their values printed ('password' includes -- 'new_password', etc.) local censored_names = { password = true; passwd = true; pass = true; pwd = true; }; local optimal_line_length = 65; local termcolours = require "util.termcolours"; local getstring = termcolours.getstring; local styles; do _ = termcolours.getstyle; styles = { boundary_padding = _("bright"); filename = _("bright", "blue"); level_num = _("green"); funcname = _("yellow"); location = _("yellow"); }; end module("debugx", package.seeall); function get_locals_table(level) level = level + 1; -- Skip this function itself local locals = {}; for local_num = 1, math.huge do local name, value = debug.getlocal(level, local_num); if not name then break; end table.insert(locals, { name = name, value = value }); end return locals; end function get_upvalues_table(func) local upvalues = {}; if func then for upvalue_num = 1, math.huge do local name, value = debug.getupvalue(func, upvalue_num); if not name then break; end table.insert(upvalues, { name = name, value = value }); end end return upvalues; end function string_from_var_table(var_table, max_line_len, indent_str) local var_string = {}; local col_pos = 0; max_line_len = max_line_len or math.huge; indent_str = "\n"..(indent_str or ""); for _, var in ipairs(var_table) do local name, value = var.name, var.value; if name:sub(1,1) ~= "(" then if type(value) == "string" then if censored_names[name:match("%a+$")] then value = ""; else value = ("%q"):format(value); end else value = tostring(value); end if #value > max_line_len then value = value:sub(1, max_line_len-3).."…"; end local str = ("%s = %s"):format(name, tostring(value)); col_pos = col_pos + #str; if col_pos > max_line_len then table.insert(var_string, indent_str); col_pos = 0; end table.insert(var_string, str); end end if #var_string == 0 then return nil; else return "{ "..table.concat(var_string, ", "):gsub(indent_str..", ", indent_str).." }"; end end function get_traceback_table(thread, start_level) local levels = {}; for level = start_level, math.huge do local info; if thread then info = debug.getinfo(thread, level+1); else info = debug.getinfo(level+1); end if not info then break; end levels[(level-start_level)+1] = { level = level; info = info; locals = get_locals_table(level+1); upvalues = get_upvalues_table(info.func); }; end return levels; end function traceback(...) local ok, ret = pcall(_traceback, ...); if not ok then return "Error in error handling: "..ret; end return ret; end local function build_source_boundary_marker(last_source_desc) local padding = string.rep("-", math.floor(((optimal_line_length - 6) - #last_source_desc)/2)); return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v ")); end function _traceback(thread, message, level) -- Lua manual says: debug.traceback ([thread,] [message [, level]]) -- I fathom this to mean one of: -- () -- (thread) -- (message, level) -- (thread, message, level) if thread == nil then -- Defaults thread, message, level = coroutine.running(), message, level; elseif type(thread) == "string" then thread, message, level = coroutine.running(), thread, message; elseif type(thread) ~= "thread" then return nil; -- debug.traceback() does this end level = level or 1; message = message and (message.."\n") or ""; -- +3 counts for this function, and the pcall() and wrapper above us local levels = get_traceback_table(thread, level+3); local last_source_desc; local lines = {}; for nlevel, level in ipairs(levels) do local info = level.info; local line = "..."; local func_type = info.namewhat.." "; local source_desc = (info.short_src == "[C]" and "C code") or info.short_src or "Unknown"; if func_type == " " then func_type = ""; end; if info.short_src == "[C]" then line = "[ C ] "..func_type.."C function "..getstring(styles.location, (info.name and ("%q"):format(info.name) or "(unknown name)")); elseif info.what == "main" then line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline); else local name = info.name or " "; if name ~= " " then name = ("%q"):format(name); end if func_type == "global " or func_type == "local " then func_type = func_type.."function "; end line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline).." in "..func_type..getstring(styles.funcname, name).." (defined on line "..info.linedefined..")"; end if source_desc ~= last_source_desc then -- Venturing into a new source, add marker for previous last_source_desc = source_desc; table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc)); end nlevel = nlevel-1; table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line); local npadding = (" "):rep(#tostring(nlevel)); local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t "..npadding); if locals_str then table.insert(lines, "\t "..npadding.."Locals: "..locals_str); end local upvalues_str = string_from_var_table(level.upvalues, optimal_line_length, "\t "..npadding); if upvalues_str then table.insert(lines, "\t "..npadding.."Upvals: "..upvalues_str); end end -- table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc)); return message.."stack traceback:\n"..table.concat(lines, "\n"); end function use() debug.traceback = traceback; end return _M; prosody-0.9.1/util/pluginloader.lua0000644000175000017500000000322512213321667017276 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)"); local plugin_dir = {}; for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do path = path..dir_sep; -- add path separator to path end path = path:gsub(dir_sep..dir_sep.."+", dir_sep); -- coalesce multiple separaters plugin_dir[#plugin_dir + 1] = path; end local io_open = io.open; local envload = require "util.envload".envload; module "pluginloader" function load_file(names) local file, err, path; for i=1,#plugin_dir do for j=1,#names do path = plugin_dir[i]..names[j]; file, err = io_open(path); if file then local content = file:read("*a"); file:close(); return content, path; end end end return file, err; end function load_resource(plugin, resource) resource = resource or "mod_"..plugin..".lua"; local names = { "mod_"..plugin.."/"..plugin.."/"..resource; -- mod_hello/hello/mod_hello.lua "mod_"..plugin.."/"..resource; -- mod_hello/mod_hello.lua plugin.."/"..resource; -- hello/mod_hello.lua resource; -- mod_hello.lua }; return load_file(names); end function load_code(plugin, resource, env) local content, err = load_resource(plugin, resource); if not content then return content, err; end local path = err; local f, err = envload(content, "@"..path, env); if not f then return f, err; end return f, path; end return _M; prosody-0.9.1/util/ip.lua0000644000175000017500000001212612213321667015221 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2011 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local ip_methods = {}; local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end, __tostring = function (ip) return ip.addr; end, __eq = function (ipA, ipB) return ipA.addr == ipB.addr; end}; local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" }; local function new_ip(ipStr, proto) if proto ~= "IPv4" and proto ~= "IPv6" then return nil, "invalid protocol"; end if proto == "IPv6" and ipStr:find('.', 1, true) then local changed; ipStr, changed = ipStr:gsub(":(%d+)%.(%d+)%.(%d+)%.(%d+)$", function(a,b,c,d) return (":%04X:%04X"):format(a*256+b,c*256+d); end); if changed ~= 1 then return nil, "invalid-address"; end end return setmetatable({ addr = ipStr, proto = proto }, ip_mt); end local function toBits(ip) local result = ""; local fields = {}; if ip.proto == "IPv4" then ip = ip.toV4mapped; end ip = (ip.addr):upper(); ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end); if not ip:match(":$") then fields[#fields] = nil; end for i, field in ipairs(fields) do if field:len() == 0 and i ~= 1 and i ~= #fields then for i = 1, 16 * (9 - #fields) do result = result .. "0"; end else for i = 1, 4 - field:len() do result = result .. "0000"; end for i = 1, field:len() do result = result .. hex2bits[field:sub(i,i)]; end end end return result; end local function commonPrefixLength(ipA, ipB) ipA, ipB = toBits(ipA), toBits(ipB); for i = 1, 128 do if ipA:sub(i,i) ~= ipB:sub(i,i) then return i-1; end end return 128; end local function v4scope(ip) local fields = {}; ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end); -- Loopback: if fields[1] == 127 then return 0x2; -- Link-local unicast: elseif fields[1] == 169 and fields[2] == 254 then return 0x2; -- Global unicast: else return 0xE; end end local function v6scope(ip) -- Loopback: if ip:match("^[0:]*1$") then return 0x2; -- Link-local unicast: elseif ip:match("^[Ff][Ee][89ABab]") then return 0x2; -- Site-local unicast: elseif ip:match("^[Ff][Ee][CcDdEeFf]") then return 0x5; -- Multicast: elseif ip:match("^[Ff][Ff]") then return tonumber("0x"..ip:sub(4,4)); -- Global unicast: else return 0xE; end end local function label(ip) if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then return 0; elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then return 2; elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then return 5; elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then return 13; elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then return 11; elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then return 12; elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then return 3; elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then return 4; else return 1; end end local function precedence(ip) if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then return 50; elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then return 30; elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then return 5; elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then return 3; elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then return 1; elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then return 1; elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then return 1; elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then return 35; else return 40; end end local function toV4mapped(ip) local fields = {}; local ret = "::ffff:"; ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end); ret = ret .. ("%02x"):format(fields[1]); ret = ret .. ("%02x"):format(fields[2]); ret = ret .. ":" ret = ret .. ("%02x"):format(fields[3]); ret = ret .. ("%02x"):format(fields[4]); return new_ip(ret, "IPv6"); end function ip_methods:toV4mapped() if self.proto ~= "IPv4" then return nil, "No IPv4 address" end local value = toV4mapped(self.addr); self.toV4mapped = value; return value; end function ip_methods:label() local value; if self.proto == "IPv4" then value = label(self.toV4mapped); else value = label(self); end self.label = value; return value; end function ip_methods:precedence() local value; if self.proto == "IPv4" then value = precedence(self.toV4mapped); else value = precedence(self); end self.precedence = value; return value; end function ip_methods:scope() local value; if self.proto == "IPv4" then value = v4scope(self.addr); else value = v6scope(self.addr); end self.scope = value; return value; end return {new_ip = new_ip, commonPrefixLength = commonPrefixLength}; prosody-0.9.1/util/template.lua0000644000175000017500000000542412213321667016427 0ustar matthewmatthew local stanza_mt = require "util.stanza".stanza_mt; local setmetatable = setmetatable; local pairs = pairs; local ipairs = ipairs; local error = error; local loadstring = loadstring; local debug = debug; local t_remove = table.remove; local parse_xml = require "util.xml".parse; module("template") local function trim_xml(stanza) for i=#stanza,1,-1 do local child = stanza[i]; if child.name then trim_xml(child); else child = child:gsub("^%s*", ""):gsub("%s*$", ""); stanza[i] = child; if child == "" then t_remove(stanza, i); end end end end local function create_string_string(str) str = ("%q"):format(str); str = str:gsub("{([^}]*)}", function(s) return '"..(data["'..s..'"]or"").."'; end); return str; end local function create_attr_string(attr, xmlns) local str = '{'; for name,value in pairs(attr) do if name ~= "xmlns" or value ~= xmlns then str = str..("[%q]=%s;"):format(name, create_string_string(value)); end end return str..'}'; end local function create_clone_string(stanza, lookup, xmlns) if not lookup[stanza] then local s = ('setmetatable({name=%q,attr=%s,tags={'):format(stanza.name, create_attr_string(stanza.attr, xmlns)); -- add tags for i,tag in ipairs(stanza.tags) do s = s..create_clone_string(tag, lookup, stanza.attr.xmlns)..";"; end s = s..'};'; -- add children for i,child in ipairs(stanza) do if child.name then s = s..create_clone_string(child, lookup, stanza.attr.xmlns)..";"; else s = s..create_string_string(child)..";" end end s = s..'}, stanza_mt)'; s = s:gsub('%.%.""', ""):gsub('([=;])""%.%.', "%1"):gsub(';"";', ";"); -- strip empty strings local n = #lookup + 1; lookup[n] = s; lookup[stanza] = "_"..n; end return lookup[stanza]; end local function create_cloner(stanza, chunkname) local lookup = {}; local name = create_clone_string(stanza, lookup, ""); local f = "local setmetatable,stanza_mt=...;return function(data)"; for i=1,#lookup do f = f.."local _"..i.."="..lookup[i]..";"; end f = f.."return "..name..";end"; local f,err = loadstring(f, chunkname); if not f then error(err); end return f(setmetatable, stanza_mt); end local template_mt = { __tostring = function(t) return t.name end }; local function create_template(templates, text) local stanza, err = parse_xml(text); if not stanza then error(err); end trim_xml(stanza); local info = debug.getinfo(3, "Sl"); info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)"; local template = setmetatable({ apply = create_cloner(stanza, info), name = info, text = text }, template_mt); templates[text] = template; return template; end local templates = setmetatable({}, { __mode = 'k', __index = create_template }); return function(text) return templates[text]; end; prosody-0.9.1/util/prosodyctl.lua0000644000175000017500000001333112213321667017012 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; local encodings = require "util.encodings"; local stringprep = encodings.stringprep; local storagemanager = require "core.storagemanager"; local usermanager = require "core.usermanager"; local signal = require "util.signal"; local set = require "util.set"; local lfs = require "lfs"; local pcall = pcall; local type = type; local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep; local io, os = io, os; local print = print; local tostring, tonumber = tostring, tonumber; local CFG_SOURCEDIR = _G.CFG_SOURCEDIR; local _G = _G; local prosody = prosody; module "prosodyctl" -- UI helpers function show_message(msg, ...) print(msg:format(...)); end function show_warning(msg, ...) print(msg:format(...)); end function show_usage(usage, desc) print("Usage: ".._G.arg[0].." "..usage); if desc then print(" "..desc); end end function getchar(n) local stty_ret = os.execute("stty raw -echo 2>/dev/null"); local ok, char; if stty_ret == 0 then ok, char = pcall(io.read, n or 1); os.execute("stty sane"); else ok, char = pcall(io.read, "*l"); if ok then char = char:sub(1, n or 1); end end if ok then return char; end end function getline() local ok, line = pcall(io.read, "*l"); if ok then return line; end end function getpass() local stty_ret = os.execute("stty -echo 2>/dev/null"); if stty_ret ~= 0 then io.write("\027[08m"); -- ANSI 'hidden' text attribute end local ok, pass = pcall(io.read, "*l"); if stty_ret == 0 then os.execute("stty sane"); else io.write("\027[00m"); end io.write("\n"); if ok then return pass; end end function show_yesno(prompt) io.write(prompt, " "); local choice = getchar():lower(); io.write("\n"); if not choice:match("%a") then choice = prompt:match("%[.-(%U).-%]$"); if not choice then return nil; end end return (choice == "y"); end function read_password() local password; while true do io.write("Enter new password: "); password = getpass(); if not password then show_message("No password - cancelled"); return; end io.write("Retype new password: "); if getpass() ~= password then if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then return; end else break; end end return password; end function show_prompt(prompt) io.write(prompt, " "); local line = getline(); line = line and line:gsub("\n$",""); return (line and #line > 0) and line or nil; end -- Server control function adduser(params) local user, host, password = nodeprep(params.user), nameprep(params.host), params.password; if not user then return false, "invalid-username"; elseif not host then return false, "invalid-hostname"; end local host_session = prosody.hosts[host]; 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 local ok, errmsg = usermanager.create_user(user, password, host); if not ok then return false, errmsg; end return true; 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 return usermanager.user_exists(user, host); end function passwd(params) if not _M.user_exists(params) then return false, "no-such-user"; end return _M.adduser(params); end function deluser(params) if not _M.user_exists(params) then return false, "no-such-user"; end local user, host = nodeprep(params.user), nameprep(params.host); return usermanager.delete_user(user, host); end function getpid() local pidfile = config.get("*", "pidfile"); if not pidfile then return false, "no-pidfile"; end local modules_enabled = set.new(config.get("*", "modules_enabled")); if not modules_enabled:contains("posix") then return false, "no-posix"; end local file, err = io.open(pidfile, "r+"); if not file then return false, "pidfile-read-failed", err; end local locked, err = lfs.lock(file, "w"); if locked then file:close(); return false, "pidfile-not-locked"; end local pid = tonumber(file:read("*a")); file:close(); if not pid then return false, "invalid-pid"; end return true, pid; end function isrunning() local ok, pid, err = _M.getpid(); if not ok then if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then -- Report as not running, since we can't open the pidfile -- (it probably doesn't exist) return true, false; end return ok, pid; end return true, signal.kill(pid, 0) == 0; end function start() local ok, ret = _M.isrunning(); if not ok then return ok, ret; end if ret then return false, "already-running"; end if not CFG_SOURCEDIR then os.execute("./prosody"); else os.execute(CFG_SOURCEDIR.."/../../bin/prosody"); end return true; end function stop() local ok, ret = _M.isrunning(); if not ok then return ok, ret; end if not ret then return false, "not-running"; end local ok, pid = _M.getpid() if not ok then return false, pid; end signal.kill(pid, signal.SIGTERM); return true; end function reload() local ok, ret = _M.isrunning(); if not ok then return ok, ret; end if not ret then return false, "not-running"; end local ok, pid = _M.getpid() if not ok then return false, pid; end signal.kill(pid, signal.SIGHUP); return true; end return _M; prosody-0.9.1/util/import.lua0000644000175000017500000000074612213321667016130 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert = table.insert; function import(module, ...) local m = package.loaded[module] or require(module); if type(m) == "table" and ... then local ret = {}; for _, f in ipairs{...} do t_insert(ret, m[f]); end return unpack(ret); end return m; end prosody-0.9.1/util/datetime.lua0000644000175000017500000000267612213321667016416 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- XEP-0082: XMPP Date and Time Profiles local os_date = os.date; local os_time = os.time; local os_difftime = os.difftime; local error = error; local tonumber = tonumber; module "datetime" function date(t) return os_date("!%Y-%m-%d", t); end function datetime(t) return os_date("!%Y-%m-%dT%H:%M:%SZ", t); end function time(t) return os_date("!%H:%M:%S", t); end function legacy(t) return os_date("!%Y%m%dT%H:%M:%S", t); end function parse(s) if s then local year, month, day, hour, min, sec, tzd; year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$"); if year then local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone local tzd_offset = 0; if tzd ~= "" and tzd ~= "Z" then local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)"); if not sign then return; end if #m ~= 2 then m = "0"; end h, m = tonumber(h), tonumber(m); tzd_offset = h * 60 * 60 + m * 60; if sign == "-" then tzd_offset = -tzd_offset; end end sec = (sec + time_offset) - tzd_offset; return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false}); end end end return _M; prosody-0.9.1/util/throttle.lua0000644000175000017500000000170112213321667016453 0ustar matthewmatthew local gettime = require "socket".gettime; local setmetatable = setmetatable; local floor = math.floor; module "throttle" local throttle = {}; local throttle_mt = { __index = throttle }; function throttle:update() local newt = gettime(); local elapsed = newt - self.t; self.t = newt; local balance = floor(self.rate * elapsed) + self.balance; if balance > self.max then self.balance = self.max; else self.balance = balance; end return self.balance; end function throttle:peek(cost) cost = cost or 1; return self.balance >= cost or self:update() >= cost; end function throttle:poll(cost, split) if self:peek(cost) then self.balance = self.balance - cost; return true; else local balance = self.balance; if split then self.balance = 0; end return false, balance, (cost-balance); end end function create(max, period) return setmetatable({ rate = max / period, max = max, t = 0, balance = max }, throttle_mt); end return _M; prosody-0.9.1/util/sasl_cyrus.lua0000644000175000017500000001436012213321667017002 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2009 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local cyrussasl = require "cyrussasl"; local log = require "util.logger".init("sasl_cyrus"); local setmetatable = setmetatable local pcall = pcall local s_match, s_gmatch = string.match, string.gmatch local sasl_errstring = { -- SASL result codes -- [1] = "another step is needed in authentication"; [0] = "successful result"; [-1] = "generic failure"; [-2] = "memory shortage failure"; [-3] = "overflowed buffer"; [-4] = "mechanism not supported"; [-5] = "bad protocol / cancel"; [-6] = "can't request info until later in exchange"; [-7] = "invalid parameter supplied"; [-8] = "transient failure (e.g., weak key)"; [-9] = "integrity check failed"; [-12] = "SASL library not initialized"; -- client only codes -- [2] = "needs user interaction"; [-10] = "server failed mutual authentication step"; [-11] = "mechanism doesn't support requested feature"; -- server only codes -- [-13] = "authentication failure"; [-14] = "authorization failure"; [-15] = "mechanism too weak for this user"; [-16] = "encryption needed to use mechanism"; [-17] = "One time use of a plaintext password will enable requested mechanism for user"; [-18] = "passphrase expired, has to be reset"; [-19] = "account disabled"; [-20] = "user not found"; [-23] = "version mismatch with plug-in"; [-24] = "remote authentication server unavailable"; [-26] = "user exists, but no verifier for user"; -- codes for password setting -- [-21] = "passphrase locked"; [-22] = "requested change was not needed"; [-27] = "passphrase is too weak for security policy"; [-28] = "user supplied passwords not permitted"; }; setmetatable(sasl_errstring, { __index = function() return "undefined error!" end }); module "sasl_cyrus" local method = {}; method.__index = method; local initialized = false; local function init(service_name) if not initialized then local st, errmsg = pcall(cyrussasl.server_init, service_name); if st then initialized = true; else log("error", "Failed to initialize Cyrus SASL: %s", errmsg); end end end -- create a new SASL object which can be used to authenticate clients -- host_fqdn may be nil in which case gethostname() gives the value. -- For GSSAPI, this determines the hostname in the service ticket (after -- reverse DNS canonicalization, only if [libdefaults] rdns = true which -- is the default). function new(realm, service_name, app_name, host_fqdn) init(app_name or service_name); local st, ret = pcall(cyrussasl.server_new, service_name, host_fqdn, realm, nil, nil) if not st then log("error", "Creating SASL server connection failed: %s", ret); return nil; end local sasl_i = { realm = realm, service_name = service_name, cyrus = ret }; if cyrussasl.set_canon_cb then local c14n_cb = function (user) local node = s_match(user, "^([^@]+)"); log("debug", "Canonicalizing username %s to %s", user, node) return node end cyrussasl.set_canon_cb(sasl_i.cyrus, c14n_cb); end cyrussasl.setssf(sasl_i.cyrus, 0, 0xffffffff) local mechanisms = {}; local cyrus_mechs = cyrussasl.listmech(sasl_i.cyrus, nil, "", " ", ""); for w in s_gmatch(cyrus_mechs, "[^ ]+") do mechanisms[w] = true; end sasl_i.mechs = mechanisms; return setmetatable(sasl_i, method); end -- get a fresh clone with the same realm and service name function method:clean_clone() return new(self.realm, self.service_name) end -- get a list of possible SASL mechanims to use function method:mechanisms() return self.mechs; end -- select a mechanism to use function method:select(mechanism) if not self.selected and self.mechs[mechanism] then self.selected = mechanism; return true; end end -- feed new messages to process into the library function method:process(message) local err; local data; if not self.first_step_done then err, data = cyrussasl.server_start(self.cyrus, self.selected, message or "") self.first_step_done = true; else err, data = cyrussasl.server_step(self.cyrus, message or "") end self.username = cyrussasl.get_username(self.cyrus) if (err == 0) then -- SASL_OK if self.require_provisioning and not self.require_provisioning(self.username) then return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP"; end return "success", data elseif (err == 1) then -- SASL_CONTINUE return "challenge", data elseif (err == -4) then -- SASL_NOMECH log("debug", "SASL mechanism not available from remote end") return "failure", "invalid-mechanism", "SASL mechanism not available" elseif (err == -13) then -- SASL_BADAUTH return "failure", "not-authorized", sasl_errstring[err]; else log("debug", "Got SASL error condition %d: %s", err, sasl_errstring[err]); return "failure", "undefined-condition", sasl_errstring[err]; end end return _M; prosody-0.9.1/util/termcolours.lua0000644000175000017500000000555212213321667017174 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_concat, t_insert = table.concat, table.insert; local char, format = string.char, string.format; local tonumber = tonumber; local ipairs = ipairs; local io_write = io.write; local windows; if os.getenv("WINDIR") then windows = require "util.windows"; end local orig_color = windows and windows.get_consolecolor and windows.get_consolecolor(); module "termcolours" local stylemap = { reset = 0; bright = 1, dim = 2, underscore = 4, blink = 5, reverse = 7, hidden = 8; black = 30; red = 31; green = 32; yellow = 33; blue = 34; magenta = 35; cyan = 36; white = 37; ["black background"] = 40; ["red background"] = 41; ["green background"] = 42; ["yellow background"] = 43; ["blue background"] = 44; ["magenta background"] = 45; ["cyan background"] = 46; ["white background"] = 47; bold = 1, dark = 2, underline = 4, underlined = 4, normal = 0; } local winstylemap = { ["0"] = orig_color, -- reset ["1"] = 7+8, -- bold ["1;33"] = 2+4+8, -- bold yellow ["1;31"] = 4+8 -- bold red } local cssmap = { [1] = "font-weight: bold", [2] = "opacity: 0.5", [4] = "text-decoration: underline", [8] = "visibility: hidden", [30] = "color:black", [31] = "color:red", [32]="color:green", [33]="color:#FFD700", [34] = "color:blue", [35] = "color: magenta", [36] = "color:cyan", [37] = "color: white", [40] = "background-color:black", [41] = "background-color:red", [42]="background-color:green", [43]="background-color:yellow", [44] = "background-color:blue", [45] = "background-color: magenta", [46] = "background-color:cyan", [47] = "background-color: white"; }; local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m"; function getstring(style, text) if style then return format(fmt_string, style, text); else return text; end end function getstyle(...) local styles, result = { ... }, {}; for i, style in ipairs(styles) do style = stylemap[style]; if style then t_insert(result, style); end end return t_concat(result, ";"); end local last = "0"; function setstyle(style) style = style or "0"; if style ~= last then io_write("\27["..style.."m"); last = style; end end if windows then function setstyle(style) style = style or "0"; if style ~= last then windows.set_consolecolor(winstylemap[style] or orig_color); last = style; end end if not orig_color then function setstyle(style) end end end local function ansi2css(ansi_codes) if ansi_codes == "0" then return ""; end local css = {}; for code in ansi_codes:gmatch("[^;]+") do t_insert(css, cssmap[tonumber(code)]); end return ""; end function tohtml(input) return input:gsub("\027%[(.-)m", ansi2css); end return _M; prosody-0.9.1/util/serialization.lua0000644000175000017500000000460112213321667017465 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local string_rep = string.rep; local type = type; local tostring = tostring; local t_insert = table.insert; local t_concat = table.concat; local error = error; local pairs = pairs; local next = next; local loadstring = loadstring; local pcall = pcall; local debug_traceback = debug.traceback; local log = require "util.logger".init("serialization"); local envload = require"util.envload".envload; module "serialization" local indent = function(i) return string_rep("\t", i); end local function basicSerialize (o) if type(o) == "number" or type(o) == "boolean" then -- no need to check for NaN, as that's not a valid table index if o == 1/0 then return "(1/0)"; elseif o == -1/0 then return "(-1/0)"; else return tostring(o); end else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise. return (("%q"):format(tostring(o)):gsub("\\\n", "\\n")); end end local function _simplesave(o, ind, t, func) if type(o) == "number" then if o ~= o then func(t, "(0/0)"); elseif o == 1/0 then func(t, "(1/0)"); elseif o == -1/0 then func(t, "(-1/0)"); else func(t, tostring(o)); end elseif type(o) == "string" then func(t, (("%q"):format(o):gsub("\\\n", "\\n"))); elseif type(o) == "table" then if next(o) ~= nil then func(t, "{\n"); for k,v in pairs(o) do func(t, indent(ind)); func(t, "["); func(t, basicSerialize(k)); func(t, "] = "); if ind == 0 then _simplesave(v, 0, t, func); else _simplesave(v, ind+1, t, func); end func(t, ";\n"); end func(t, indent(ind-1)); func(t, "}"); else func(t, "{}"); end elseif type(o) == "boolean" then func(t, (o and "true" or "false")); else log("error", "cannot serialize a %s: %s", type(o), debug_traceback()) func(t, "nil"); end end function append(t, o) _simplesave(o, 1, t, t.write or t_insert); return t; end function serialize(o) return t_concat(append({}, o)); end function deserialize(str) if type(str) ~= "string" then return nil; end str = "return "..str; local f, err = envload(str, "@data", {}); if not f then return nil, err; end local success, ret = pcall(f); if not success then return nil, ret; end return ret; end return _M; prosody-0.9.1/util/helpers.lua0000644000175000017500000000436112213321667016255 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local debug = require "util.debug"; module("helpers", package.seeall); -- Helper functions for debugging local log = require "util.logger".init("util.debug"); function log_host_events(host) return log_events(prosody.hosts[host].events, host); end function revert_log_host_events(host) return revert_log_events(prosody.hosts[host].events); end function log_events(events, name, logger) local f = events.fire_event; if not f then error("Object does not appear to be a util.events object"); end logger = logger or log; name = name or tostring(events); function events.fire_event(event, ...) logger("debug", "%s firing event: %s", name, event); return f(event, ...); end events[events.fire_event] = f; return events; end function revert_log_events(events) events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)) end function show_events(events, specific_event) local event_handlers = events._handlers; local events_array = {}; local event_handler_arrays = {}; for event in pairs(events._event_map) do local handlers = event_handlers[event]; if handlers and (event == specific_event or not specific_event) then table.insert(events_array, event); local handler_strings = {}; for i, handler in ipairs(handlers) do local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler)); handler_strings[i] = " "..i..": "..tostring(handler)..(upvals and ("\n "..upvals) or ""); end event_handler_arrays[event] = handler_strings; end end table.sort(events_array); local i = 1; while i <= #events_array do local handlers = event_handler_arrays[events_array[i]]; for j=#handlers, 1, -1 do table.insert(events_array, i+1, handlers[j]); end if i > 1 then events_array[i] = "\n"..events_array[i]; end i = i + #handlers + 1 end return table.concat(events_array, "\n"); end function get_upvalue(f, get_name) local i, name, value = 0; repeat i = i + 1; name, value = debug.getupvalue(f, i); until name == get_name or name == nil; return value; end return _M; prosody-0.9.1/util/dependencies.lua0000644000175000017500000001201112213321667017230 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- module("dependencies", package.seeall) function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end -- Required to be able to find packages installed with luarocks if not softreq "luarocks.loader" then -- LuaRocks 2.x softreq "luarocks.require"; -- LuaRocks <1.x end function missingdep(name, sources, msg) print(""); print("**************************"); print("Prosody was unable to find "..tostring(name)); print("This package can be obtained in the following ways:"); print(""); local longest_platform = 0; for platform in pairs(sources) do longest_platform = math.max(longest_platform, #platform); end for platform, source in pairs(sources) do print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source); end print(""); print(msg or (name.." is required for Prosody to run, so we will now exit.")); print("More help can be found on our website, at http://prosody.im/doc/depends"); print("**************************"); print(""); end -- COMPAT w/pre-0.8 Debian: The Debian config file used to use -- util.ztact, which has been removed from Prosody in 0.8. This -- is to log an error for people who still use it, so they can -- update their configs. package.preload["util.ztact"] = function () if not package.loaded["core.loggingmanager"] then error("util.ztact has been removed from Prosody and you need to fix your config " .."file. More information can be found at http://prosody.im/doc/packagers#ztact", 0); else error("module 'util.ztact' has been deprecated in Prosody 0.8."); end end; function check_dependencies() local fatal; local lxp = softreq "lxp" if not lxp then missingdep("luaexpat", { ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0"; ["luarocks"] = "luarocks install luaexpat"; ["Source"] = "http://www.keplerproject.org/luaexpat/"; }); fatal = true; end local socket = softreq "socket" if not socket then missingdep("luasocket", { ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2"; ["luarocks"] = "luarocks install luasocket"; ["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/"; }); fatal = true; end local lfs, err = softreq "lfs" if not lfs then missingdep("luafilesystem", { ["luarocks"] = "luarocks install luafilesystem"; ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-filesystem0"; ["Source"] = "http://www.keplerproject.org/luafilesystem/"; }); fatal = true; end local ssl = softreq "ssl" if not ssl then missingdep("LuaSec", { ["Debian/Ubuntu"] = "http://prosody.im/download/start#debian_and_ubuntu"; ["luarocks"] = "luarocks install luasec"; ["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/"; }, "SSL/TLS support will not be available"); end local encodings, err = softreq "util.encodings" if not encodings then if err:match("not found") then missingdep("util.encodings", { ["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/"; ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so"; }); else print "***********************************" print("util/encodings couldn't be loaded. Check that you have a recent version of libidn"); print "" print("The full error was:"); print(err) print "***********************************" end fatal = true; end local hashes, err = softreq "util.hashes" if not hashes then if err:match("not found") then missingdep("util.hashes", { ["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/"; ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so"; }); else print "***********************************" print("util/hashes couldn't be loaded. Check that you have a recent version of OpenSSL (libcrypto in particular)"); print "" print("The full error was:"); print(err) print "***********************************" end fatal = true; end return not fatal; end function log_warnings() if ssl then local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)"); if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends"); end end if lxp then if not pcall(lxp.new, { StartDoctypeDecl = false }) then log("error", "The version of LuaExpat on your system leaves Prosody " .."vulnerable to denial-of-service attacks. You should upgrade to " .."LuaExpat 1.1.1 or higher as soon as possible. See " .."http://prosody.im/doc/depends#luaexpat for more information."); end end end return _M; prosody-0.9.1/util/iterators.lua0000644000175000017500000000620212213321667016623 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- --[[ Iterators ]]-- local it = {}; -- Reverse an iterator function it.reverse(f, s, var) local results = {}; -- First call the normal iterator while true do local ret = { f(s, var) }; var = ret[1]; if var == nil then break; end table.insert(results, 1, ret); end -- Then return our reverse one local i,max = 0, #results; return function (results) if i= n then return nil; end c = c + 1; return f(s, var); end, s; end -- Skip the first n items an iterator returns function it.skip(n, f, s, var) for i=1,n do var = f(s, var); end return f, s, var; end -- Return the last n items an iterator returns function it.tail(n, f, s, var) local results, count = {}, 0; while true do local ret = { f(s, var) }; var = ret[1]; if var == nil then break; end results[(count%n)+1] = ret; count = count + 1; end if n > count then n = count; end local pos = 0; return function () pos = pos + 1; if pos > n then return nil; end return unpack(results[((count-1+pos)%n)+1]); end --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 return _range_iter, y, x-1; end -- Convert the values returned by an iterator to an array function it.to_array(f, s, var) local t, var = {}; while true do var = f(s, var); if var == nil then break; end table.insert(t, var); end return t; end -- Treat the return of an iterator as key,value pairs, -- and build a table function it.to_table(f, s, var) local t, var2 = {}; while true do var, var2 = f(s, var); if var == nil then break; end t[var] = var2; end return t; end return it; prosody-0.9.1/util/multitable.lua0000644000175000017500000000701012213321667016747 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local select = select; local t_insert = table.insert; local unpack, pairs, next, type = unpack, pairs, next, type; module "multitable" local function get(self, ...) local t = self.data; for n = 1,select('#', ...) do t = t[select(n, ...)]; if not t then break; end end return t; end local function add(self, ...) local t = self.data; local count = select('#', ...); for n = 1,count-1 do local key = select(n, ...); local tab = t[key]; if not tab then tab = {}; t[key] = tab; end t = tab; end t_insert(t, (select(count, ...))); end local function set(self, ...) local t = self.data; local count = select('#', ...); for n = 1,count-2 do local key = select(n, ...); local tab = t[key]; if not tab then tab = {}; t[key] = tab; end t = tab; end t[(select(count-1, ...))] = (select(count, ...)); end local function r(t, n, _end, ...) if t == nil then return; end local k = select(n, ...); if n == _end then t[k] = nil; return; end if k then local v = t[k]; if v then r(v, n+1, _end, ...); if not next(v) then t[k] = nil; end end else for _,b in pairs(t) do r(b, n+1, _end, ...); if not next(b) then t[_] = nil; end end end end local function remove(self, ...) local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end r(self.data, 1, _end, ...); end local function s(t, n, results, _end, ...) if t == nil then return; end local k = select(n, ...); if n == _end then if k == nil then for _, v in pairs(t) do t_insert(results, v); end else t_insert(results, t[k]); end return; end if k then local v = t[k]; if v then s(v, n+1, results, _end, ...); end else for _,b in pairs(t) do s(b, n+1, results, _end, ...); end end end -- Search for keys, nil == wildcard local function search(self, ...) local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end local results = {}; s(self.data, 1, results, _end, ...); return results; end -- Append results to an existing list local function search_add(self, results, ...) if not results then results = {}; end local _end = select('#', ...); for n = _end,1 do if select(n, ...) then _end = n; break; end end s(self.data, 1, results, _end, ...); return results; end function iter(self, ...) local query = { ... }; local maxdepth = select("#", ...); local stack = { self.data }; local keys = { }; local function it(self) local depth = #stack; local key = next(stack[depth], keys[depth]); if key == nil then -- Go up the stack stack[depth], keys[depth] = nil, nil; if depth > 1 then return it(self); end return; -- The end else keys[depth] = key; end local value = stack[depth][key]; if query[depth] == nil or key == query[depth] then if depth == maxdepth then -- Result local result = {}; -- Collect keys forming path to result for i = 1, depth do result[i] = keys[i]; end result[depth+1] = value; return unpack(result, 1, depth+1); elseif type(value) == "table" then t_insert(stack, value); -- Descend end end return it(self); end; return it, self; end function new() return { data = {}; get = get; add = add; set = set; remove = remove; search = search; search_add = search_add; iter = iter; }; end return _M; prosody-0.9.1/util/set.lua0000644000175000017500000000545212213321667015410 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local ipairs, pairs, setmetatable, next, tostring = ipairs, pairs, setmetatable, next, tostring; local t_concat = table.concat; module "set" local set_mt = {}; function set_mt.__call(set, _, k) return next(set._items, k); end function set_mt.__add(set1, set2) return _M.union(set1, set2); end function set_mt.__sub(set1, set2) return _M.difference(set1, set2); end function set_mt.__div(set, func) local new_set, new_items = _M.new(); local items, new_items = set._items, new_set._items; for item in pairs(items) do local new_item = func(item); if new_item ~= nil then new_items[new_item] = true; end end return new_set; end function set_mt.__eq(set1, set2) local set1, set2 = set1._items, set2._items; for item in pairs(set1) do if not set2[item] then return false; end end for item in pairs(set2) do if not set1[item] then return false; end end return true; end function set_mt.__tostring(set) local s, items = { }, set._items; for item in pairs(items) do s[#s+1] = tostring(item); end return t_concat(s, ", "); end local items_mt = {}; function items_mt.__call(items, _, k) return next(items, k); end function new(list) local items = setmetatable({}, items_mt); local set = { _items = items }; function set:add(item) items[item] = true; end function set:contains(item) return items[item]; end function set:items() return items; end function set:remove(item) items[item] = nil; end function set:add_list(list) if list then for _, item in ipairs(list) do items[item] = true; end end end function set:include(otherset) for item in otherset do items[item] = true; end end function set:exclude(otherset) for item in otherset do items[item] = nil; end end function set:empty() return not next(items); end if list then set:add_list(list); end return setmetatable(set, set_mt); end function union(set1, set2) local set = new(); local items = set._items; for item in pairs(set1._items) do items[item] = true; end for item in pairs(set2._items) do items[item] = true; end return set; end function difference(set1, set2) local set = new(); local items = set._items; for item in pairs(set1._items) do items[item] = (not set2._items[item]) or nil; end return set; end function intersection(set1, set2) local set = new(); local items = set._items; set1, set2 = set1._items, set2._items; for item in pairs(set1) do items[item] = (not not set2[item]) or nil; end return set; end function xor(set1, set2) return union(set1, set2) - intersection(set1, set2); end return _M; prosody-0.9.1/util/events.lua0000644000175000017500000000367612213321667016127 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local pairs = pairs; local t_insert = table.insert; local t_sort = table.sort; local setmetatable = setmetatable; local next = next; module "events" function new() local handlers = {}; local event_map = {}; local function _rebuild_index(handlers, event) local _handlers = event_map[event]; if not _handlers or next(_handlers) == nil then return; end local index = {}; for handler in pairs(_handlers) do t_insert(index, handler); end t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end); handlers[event] = index; return index; end; setmetatable(handlers, { __index = _rebuild_index }); local function add_handler(event, handler, priority) local map = event_map[event]; if map then map[handler] = priority or 0; else map = {[handler] = priority or 0}; event_map[event] = map; end handlers[event] = nil; end; local function remove_handler(event, handler) local map = event_map[event]; if map then map[handler] = nil; handlers[event] = nil; if next(map) == nil then event_map[event] = nil; end end end; local function add_handlers(handlers) for event, handler in pairs(handlers) do add_handler(event, handler); end end; local function remove_handlers(handlers) for event, handler in pairs(handlers) do remove_handler(event, handler); end end; local function fire_event(event, ...) local h = handlers[event]; if h then for i=1,#h do local ret = h[i](...); if ret ~= nil then return ret; end end end end; return { add_handler = add_handler; remove_handler = remove_handler; add_handlers = add_handlers; remove_handlers = remove_handlers; fire_event = fire_event; _handlers = handlers; _event_map = event_map; }; end return _M; prosody-0.9.1/util/rfc6724.lua0000644000175000017500000000725012213321667015710 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2011-2013 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- This is used to sort destination addresses by preference -- during S2S connections. -- We can't hand this off to getaddrinfo, since it blocks local ip_commonPrefixLength = require"util.ip".commonPrefixLength local new_ip = require"util.ip".new_ip; local function commonPrefixLength(ipA, ipB) local len = ip_commonPrefixLength(ipA, ipB); return len < 64 and len or 64; end local function t_sort(t, comp) for i = 1, (#t - 1) do for j = (i + 1), #t do local a, b = t[i], t[j]; if not comp(a,b) then t[i], t[j] = b, a; end end end end local function source(dest, candidates) local function comp(ipA, ipB) -- Rule 1: Prefer same address if dest == ipA then return true; elseif dest == ipB then return false; end -- Rule 2: Prefer appropriate scope if ipA.scope < ipB.scope then if ipA.scope < dest.scope then return false; else return true; end elseif ipA.scope > ipB.scope then if ipB.scope < dest.scope then return true; else return false; end end -- Rule 3: Avoid deprecated addresses -- XXX: No way to determine this -- Rule 4: Prefer home addresses -- XXX: Mobility Address related, no way to determine this -- Rule 5: Prefer outgoing interface -- XXX: Interface to address relation. No way to determine this -- Rule 6: Prefer matching label if ipA.label == dest.label and ipB.label ~= dest.label then return true; elseif ipB.label == dest.label and ipA.label ~= dest.label then return false; end -- Rule 7: Prefer temporary addresses (over public ones) -- XXX: No way to determine this -- Rule 8: Use longest matching prefix if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then return true; else return false; end end t_sort(candidates, comp); return candidates[1]; end local function destination(candidates, sources) local sourceAddrs = {}; local function comp(ipA, ipB) local ipAsource = sourceAddrs[ipA]; local ipBsource = sourceAddrs[ipB]; -- Rule 1: Avoid unusable destinations -- XXX: No such information -- Rule 2: Prefer matching scope if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then return true; elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then return false; end -- Rule 3: Avoid deprecated addresses -- XXX: No way to determine this -- Rule 4: Prefer home addresses -- XXX: Mobility Address related, no way to determine this -- Rule 5: Prefer matching label if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then return true; elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then return false; end -- Rule 6: Prefer higher precedence if ipA.precedence > ipB.precedence then return true; elseif ipA.precedence < ipB.precedence then return false; end -- Rule 7: Prefer native transport -- XXX: No way to determine this -- Rule 8: Prefer smaller scope if ipA.scope < ipB.scope then return true; elseif ipA.scope > ipB.scope then return false; end -- Rule 9: Use longest matching prefix if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then return true; elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then return false; end -- Rule 10: Otherwise, leave order unchanged return true; end for _, ip in ipairs(candidates) do sourceAddrs[ip] = source(ip, sources); end t_sort(candidates, comp); return candidates; end return {source = source, destination = destination}; prosody-0.9.1/util/json.lua0000644000175000017500000002370612213321667015570 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local type = type; local t_insert, t_concat, t_remove, t_sort = table.insert, table.concat, table.remove, table.sort; local s_char = string.char; local tostring, tonumber = tostring, tonumber; local pairs, ipairs = pairs, ipairs; local next = next; local error = error; local newproxy, getmetatable, setmetatable = newproxy, getmetatable, setmetatable; local print = print; local has_array, array = pcall(require, "util.array"); local array_mt = has_array and getmetatable(array()) or {}; --module("json") local json = {}; local null = newproxy and newproxy(true) or {}; if getmetatable and getmetatable(null) then getmetatable(null).__tostring = function() return "null"; end; end json.null = null; local escapes = { ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"}; local unescapes = { ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"}; for i=0,31 do local ch = s_char(i); if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); 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, table = true, boolean = true }; local special_keys = { __array = true; __hash = true; }; local simplesave, tablesave, arraysave, stringsave; function stringsave(o, buffer) -- FIXME do proper utf-8 and binary data detection t_insert(buffer, "\""..(o:gsub(".", escapes)).."\""); end function arraysave(o, buffer) t_insert(buffer, "["); if next(o) then for i,v in ipairs(o) do simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); end t_insert(buffer, "]"); end function tablesave(o, buffer) local __array = {}; local __hash = {}; local hash = {}; for i,v in ipairs(o) do __array[i] = v; end for k,v in pairs(o) do local ktype, vtype = type(k), type(v); if valid_types[vtype] or v == null then if ktype == "string" and not special_keys[k] then hash[k] = v; elseif (valid_types[ktype] or k == null) and __array[k] == nil then __hash[k] = v; end end end if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then t_insert(buffer, "{"); local mark = #buffer; if buffer.ordered then local keys = {}; for k in pairs(hash) do t_insert(keys, k); end t_sort(keys); for _,k in ipairs(keys) do stringsave(k, buffer); t_insert(buffer, ":"); simplesave(hash[k], buffer); t_insert(buffer, ","); end else for k,v in pairs(hash) do stringsave(k, buffer); t_insert(buffer, ":"); simplesave(v, buffer); t_insert(buffer, ","); end end if next(__hash) ~= nil then t_insert(buffer, "\"__hash\":["); for k,v in pairs(__hash) do simplesave(k, buffer); t_insert(buffer, ","); simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); t_insert(buffer, "]"); t_insert(buffer, ","); end if next(__array) then t_insert(buffer, "\"__array\":"); arraysave(__array, buffer); t_insert(buffer, ","); end if mark ~= #buffer then t_remove(buffer); end t_insert(buffer, "}"); else arraysave(__array, buffer); end end function simplesave(o, buffer) local t = type(o); if t == "number" then t_insert(buffer, tostring(o)); elseif t == "string" then stringsave(o, buffer); elseif t == "table" then 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 t_insert(buffer, "null"); end end function json.encode(obj) local t = {}; simplesave(obj, t); return t_concat(t); end function json.encode_ordered(obj) local t = { ordered = true }; simplesave(obj, t); return t_concat(t); end function json.encode_array(obj) local t = {}; arraysave(obj, t); return t_concat(t); end ----------------------------------- local function _skip_whitespace(json, index) return json:find("[^ \t\r\n]", index) or index; -- no need to check \r\n, we converted those to \t end local function _fixobject(obj) local __array = obj.__array; if __array then obj.__array = nil; for i,v in ipairs(__array) do t_insert(obj, v); end end local __hash = obj.__hash; if __hash then obj.__hash = nil; local k; for i,v in ipairs(__hash) do if k ~= nil then obj[k] = v; k = nil; else k = v; end end end return obj; end local _readvalue, _readstring; local function _readobject(json, index) local o = {}; while true do local key, val; index = _skip_whitespace(json, index + 1); if json:byte(index) ~= 0x22 then -- "\"" if json:byte(index) == 0x7d then return o, index + 1; end -- "}" return nil, "key expected"; end key, index = _readstring(json, index); if key == nil then return nil, index; end index = _skip_whitespace(json, index); if json:byte(index) ~= 0x3a then return nil, "colon expected"; end -- ":" val, index = _readvalue(json, index + 1); if val == nil then return nil, index; end o[key] = val; index = _skip_whitespace(json, index); local b = json:byte(index); if b == 0x7d then return _fixobject(o), index + 1; end -- "}" if b ~= 0x2c then return nil, "object eof"; end -- "," end end local function _readarray(json, index) local a = {}; local oindex = index; while true do local val; val, index = _readvalue(json, index + 1); if val == nil then if json:byte(oindex + 1) == 0x5d then return setmetatable(a, array_mt), oindex + 2; end -- "]" return val, index; end t_insert(a, val); index = _skip_whitespace(json, index); local b = json:byte(index); if b == 0x5d then return setmetatable(a, array_mt), index + 1; end -- "]" if b ~= 0x2c then return nil, "array eof"; end -- "," end end local _unescape_error; local function _unescape_surrogate_func(x) local lead, trail = tonumber(x:sub(3, 6), 16), tonumber(x:sub(9, 12), 16); local codepoint = lead * 0x400 + trail - 0x35FDC00; local a = codepoint % 64; codepoint = (codepoint - a) / 64; local b = codepoint % 64; codepoint = (codepoint - b) / 64; local c = codepoint % 64; codepoint = (codepoint - c) / 64; return s_char(0xF0 + codepoint, 0x80 + c, 0x80 + b, 0x80 + a); end local function _unescape_func(x) x = x:match("%x%x%x%x", 3); if x then --if x >= 0xD800 and x <= 0xDFFF then _unescape_error = true; end -- bad surrogate pair return codepoint_to_utf8(tonumber(x, 16)); end _unescape_error = true; end function _readstring(json, index) index = index + 1; local endindex = json:find("\"", index, true); if endindex then local s = json:sub(index, endindex - 1); --if s:find("[%z-\31]") then return nil, "control char in string"; end -- FIXME handle control characters _unescape_error = nil; --s = s:gsub("\\u[dD][89abAB]%x%x\\u[dD][cdefCDEF]%x%x", _unescape_surrogate_func); -- FIXME handle escapes beyond BMP s = s:gsub("\\u.?.?.?.?", _unescape_func); if _unescape_error then return nil, "invalid escape"; end return s, endindex + 1; end return nil, "string eof"; end local function _readnumber(json, index) local m = json:match("[0-9%.%-eE%+]+", index); -- FIXME do strict checking return tonumber(m), index + #m; end local function _readnull(json, index) local a, b, c = json:byte(index + 1, index + 3); if a == 0x75 and b == 0x6c and c == 0x6c then return null, index + 4; end return nil, "null parse failed"; end local function _readtrue(json, index) local a, b, c = json:byte(index + 1, index + 3); if a == 0x72 and b == 0x75 and c == 0x65 then return true, index + 4; end return nil, "true parse failed"; end local function _readfalse(json, index) local a, b, c, d = json:byte(index + 1, index + 4); if a == 0x61 and b == 0x6c and c == 0x73 and d == 0x65 then return false, index + 5; end return nil, "false parse failed"; end function _readvalue(json, index) index = _skip_whitespace(json, index); local b = json:byte(index); -- TODO try table lookup instead of if-else? if b == 0x7B then -- "{" return _readobject(json, index); elseif b == 0x5B then -- "[" return _readarray(json, index); elseif b == 0x22 then -- "\"" return _readstring(json, index); elseif b ~= nil and b >= 0x30 and b <= 0x39 or b == 0x2d then -- "0"-"9" or "-" return _readnumber(json, index); elseif b == 0x6e then -- "n" return _readnull(json, index); elseif b == 0x74 then -- "t" return _readtrue(json, index); elseif b == 0x66 then -- "f" return _readfalse(json, index); else return nil, "value expected"; end end local first_escape = { ["\\\""] = "\\u0022"; ["\\\\"] = "\\u005c"; ["\\/" ] = "\\u002f"; ["\\b" ] = "\\u0008"; ["\\f" ] = "\\u000C"; ["\\n" ] = "\\u000A"; ["\\r" ] = "\\u000D"; ["\\t" ] = "\\u0009"; ["\\u" ] = "\\u"; }; function json.decode(json) json = json:gsub("\\.", first_escape) -- get rid of all escapes except \uXXXX, making string parsing much simpler --:gsub("[\r\n]", "\t"); -- \r\n\t are equivalent, we care about none of them, and none of them can be in strings -- TODO do encoding verification local val, index = _readvalue(json, 1); if val == nil then return val, index; end if json:find("[^ \t\r\n]", index) then return nil, "garbage at eof"; end return val; end function json.test(object) local encoded = json.encode(object); local decoded = json.decode(encoded); local recoded = json.encode(decoded); if encoded ~= recoded then print("FAILED"); print("encoded:", encoded); print("recoded:", recoded); else print(encoded); end return encoded == recoded; end return json; prosody-0.9.1/util/stanza.lua0000644000175000017500000002541312213321667016114 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert = table.insert; local t_remove = table.remove; local t_concat = table.concat; local s_format = string.format; local s_match = string.match; local tostring = tostring; local setmetatable = setmetatable; local pairs = pairs; local ipairs = ipairs; local type = type; local s_gsub = string.gsub; local s_sub = string.sub; local s_find = string.find; local os = os; local do_pretty_printing = not os.getenv("WINDIR"); local getstyle, getstring; if do_pretty_printing then local ok, termcolours = pcall(require, "util.termcolours"); if ok then getstyle, getstring = termcolours.getstyle, termcolours.getstring; else do_pretty_printing = nil; end end local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"; module "stanza" stanza_mt = { __type = "stanza" }; stanza_mt.__index = stanza_mt; local stanza_mt = stanza_mt; function stanza(name, attr) local stanza = { name = name, attr = attr or {}, tags = {} }; return setmetatable(stanza, stanza_mt); end local stanza = stanza; function stanza_mt:query(xmlns) return self:tag("query", { xmlns = xmlns }); end function stanza_mt:body(text, attr) return self:tag("body", attr):text(text); end function stanza_mt:tag(name, attrs) local s = stanza(name, attrs); local last_add = self.last_add; if not last_add then last_add = {}; self.last_add = last_add; end (last_add[#last_add] or self):add_direct_child(s); t_insert(last_add, s); return self; end function stanza_mt:text(text) local last_add = self.last_add; (last_add and last_add[#last_add] or self):add_direct_child(text); return self; end function stanza_mt:up() local last_add = self.last_add; if last_add then t_remove(last_add); end return self; end function stanza_mt:reset() self.last_add = nil; return self; end function stanza_mt:add_direct_child(child) if type(child) == "table" then t_insert(self.tags, child); end t_insert(self, child); end function stanza_mt:add_child(child) local last_add = self.last_add; (last_add and last_add[#last_add] or self):add_direct_child(child); return self; end function stanza_mt:get_child(name, xmlns) for _, child in ipairs(self.tags) do if (not name or child.name == name) and ((not xmlns and self.attr.xmlns == child.attr.xmlns) or child.attr.xmlns == xmlns) then return child; end end end function stanza_mt:get_child_text(name, xmlns) local tag = self:get_child(name, xmlns); if tag then return tag:get_text(); end return nil; end function stanza_mt:child_with_name(name) for _, child in ipairs(self.tags) do if child.name == name then return child; end end end function stanza_mt:child_with_ns(ns) for _, child in ipairs(self.tags) do if child.attr.xmlns == ns then return child; end end end function stanza_mt:children() local i = 0; return function (a) i = i + 1 return a[i]; end, self, i; end function stanza_mt:childtags(name, xmlns) local tags = self.tags; local start_i, max_i = 1, #tags; return function () for i = start_i, max_i do local v = tags[i]; if (not name or v.name == name) and ((not xmlns and self.attr.xmlns == v.attr.xmlns) or v.attr.xmlns == xmlns) then start_i = i+1; return v; end end end; end function stanza_mt:maptags(callback) local tags, curr_tag = self.tags, 1; local n_children, n_tags = #self, #tags; local i = 1; while curr_tag <= n_tags and n_tags > 0 do if self[i] == tags[curr_tag] then local ret = callback(self[i]); if ret == nil then t_remove(self, i); t_remove(tags, curr_tag); n_children = n_children - 1; n_tags = n_tags - 1; i = i - 1; curr_tag = curr_tag - 1; else self[i] = ret; tags[curr_tag] = ret; end curr_tag = curr_tag + 1; end i = i + 1; end return self; end function stanza_mt:find(path) local pos = 1; local len = #path + 1; repeat local xmlns, name, text; local char = s_sub(path, pos, pos); if char == "@" then return self.attr[s_sub(path, pos + 1)]; elseif char == "{" then xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1); end name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos); name = name ~= "" and name or nil; if pos == len then if text == "#" then return self:get_child_text(name, xmlns); end return self:get_child(name, xmlns); end self = self:get_child(name, xmlns); until not self end local xml_escape do local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end _M.xml_escape = xml_escape; end local function _dostring(t, buf, self, xml_escape, parentns) local nsid = 0; local name = t.name t_insert(buf, "<"..name); for k, v in pairs(t.attr) do if s_find(k, "\1", 1, true) then local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); nsid = nsid + 1; t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'"); elseif not(k == "xmlns" and v == parentns) then t_insert(buf, " "..k.."='"..xml_escape(v).."'"); end end local len = #t; if len == 0 then t_insert(buf, "/>"); else t_insert(buf, ">"); for n=1,len do local child = t[n]; if child.name then self(child, buf, self, xml_escape, t.attr.xmlns); else t_insert(buf, xml_escape(child)); end end t_insert(buf, ""); end end function stanza_mt.__tostring(t) local buf = {}; _dostring(t, buf, _dostring, xml_escape, nil); return t_concat(buf); end function stanza_mt.top_tag(t) local attr_string = ""; if t.attr then for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end end return s_format("<%s%s>", t.name, attr_string); end function stanza_mt.get_text(t) if #t.tags == 0 then return t_concat(t); end end function stanza_mt.get_error(stanza) local type, condition, text; local error_tag = stanza:get_child("error"); if not error_tag then return nil, nil, nil; end type = error_tag.attr.type; for _, child in ipairs(error_tag.tags) do if child.attr.xmlns == xmlns_stanzas then if not text and child.name == "text" then text = child:get_text(); elseif not condition then condition = child.name; end if condition and text then break; end end end return type, condition or "undefined-condition", text; end do local id = 0; function new_id() id = id + 1; return "lx"..id; end end function preserialize(stanza) local s = { name = stanza.name, attr = stanza.attr }; for _, child in ipairs(stanza) do if type(child) == "table" then t_insert(s, preserialize(child)); else t_insert(s, child); end end return s; end function deserialize(stanza) -- Set metatable if stanza then local attr = stanza.attr; for i=1,#attr do attr[i] = nil; end local attrx = {}; for att in pairs(attr) do if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then local ns,na = s_match(att, "^([^|]+)|(.+)$"); attrx[ns.."\1"..na] = attr[att]; attr[att] = nil; end end for a,v in pairs(attrx) do attr[a] = v; end setmetatable(stanza, stanza_mt); for _, child in ipairs(stanza) do if type(child) == "table" then deserialize(child); end end if not stanza.tags then -- Rebuild tags local tags = {}; for _, child in ipairs(stanza) do if type(child) == "table" then t_insert(tags, child); end end stanza.tags = tags; end end return stanza; end local function _clone(stanza) local attr, tags = {}, {}; for k,v in pairs(stanza.attr) do attr[k] = v; end local new = { name = stanza.name, attr = attr, tags = tags }; for i=1,#stanza do local child = stanza[i]; if child.name then child = _clone(child); t_insert(tags, child); end t_insert(new, child); end return setmetatable(new, stanza_mt); end clone = _clone; function message(attr, body) if not body then return stanza("message", attr); else return stanza("message", attr):tag("body"):text(body):up(); end end function iq(attr) if attr and not attr.id then attr.id = new_id(); end return stanza("iq", attr or { id = new_id() }); end function reply(orig) return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) }); end do local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; function error_reply(orig, type, condition, message) local t = reply(orig); t.attr.type = "error"; t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here :tag(condition, xmpp_stanzas_attr):up(); if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end return t; -- stanza ready for adding app-specific errors end end function presence(attr) return stanza("presence", attr); end if do_pretty_printing then local style_attrk = getstyle("yellow"); local style_attrv = getstyle("red"); local style_tagname = getstyle("red"); local style_punc = getstyle("magenta"); local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'"); local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">"); --local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, ""); local tag_format = top_tag_format.."%s"..getstring(style_punc, ""); function stanza_mt.pretty_print(t) local children_text = ""; for n, child in ipairs(t) do if type(child) == "string" then children_text = children_text .. xml_escape(child); else children_text = children_text .. child:pretty_print(); end end local attr_string = ""; if t.attr then for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end end return s_format(tag_format, t.name, attr_string, children_text, t.name); end function stanza_mt.pretty_top_tag(t) local attr_string = ""; if t.attr then for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end end return s_format(top_tag_format, t.name, attr_string); end else -- Sorry, fresh out of colours for you guys ;) stanza_mt.pretty_print = stanza_mt.__tostring; stanza_mt.pretty_top_tag = stanza_mt.top_tag; end return _M; prosody-0.9.1/util/http.lua0000644000175000017500000000337012213321667015571 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2013 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format, char = string.format, string.char; local pairs, ipairs, tonumber = pairs, ipairs, tonumber; local t_insert, t_concat = table.insert, table.concat; local function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); end local 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 local 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 local 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 contains_token(field, token) field = ","..field:gsub("[ \t]", ""):lower()..","; return field:find(","..token:lower()..",", 1, true) ~= nil; end return { urlencode = urlencode, urldecode = urldecode; formencode = formencode, formdecode = formdecode; contains_token = contains_token; }; prosody-0.9.1/util/xmppstream.lua0000644000175000017500000001224112213321667017007 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local lxp = require "lxp"; local st = require "util.stanza"; local stanza_mt = st.stanza_mt; local error = error; local tostring = tostring; local t_insert = table.insert; local t_concat = table.concat; local t_remove = table.remove; local setmetatable = setmetatable; -- COMPAT: w/LuaExpat 1.1.0 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false }); module "xmppstream" local new_parser = lxp.new; local xml_namespace = { ["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang"; ["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space"; ["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base"; ["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id"; }; local xmlns_streams = "http://etherx.jabber.org/streams"; local ns_separator = "\1"; local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; _M.ns_separator = ns_separator; _M.ns_pattern = ns_pattern; function new_sax_handlers(session, stream_callbacks) local xml_handlers = {}; local cb_streamopened = stream_callbacks.streamopened; local cb_streamclosed = stream_callbacks.streamclosed; local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end; local cb_handlestanza = stream_callbacks.handlestanza; local stream_ns = stream_callbacks.stream_ns or xmlns_streams; local stream_tag = stream_callbacks.stream_tag or "stream"; if stream_ns ~= "" then stream_tag = stream_ns..ns_separator..stream_tag; end local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error"); local stream_default_ns = stream_callbacks.default_ns; local stack = {}; local chardata, stanza = {}; local non_streamns_depth = 0; function xml_handlers:StartElement(tagname, attr) if stanza and #chardata > 0 then -- We have some character data in the buffer t_insert(stanza, t_concat(chardata)); chardata = {}; end local curr_ns,name = tagname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end if curr_ns ~= stream_default_ns or non_streamns_depth > 0 then attr.xmlns = curr_ns; non_streamns_depth = non_streamns_depth + 1; end for i=1,#attr do local k = attr[i]; attr[i] = nil; local xmlk = xml_namespace[k]; if xmlk then attr[xmlk] = attr[k]; attr[k] = nil; end end if not stanza then --if we are not currently inside a stanza if session.notopen then if tagname == stream_tag then non_streamns_depth = 0; if cb_streamopened then cb_streamopened(session, attr); end else -- Garbage before stream? cb_error(session, "no-stream"); end return; end if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then cb_error(session, "invalid-top-level-element"); end stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); else -- we are inside a stanza, so add a tag t_insert(stack, stanza); local oldstanza = stanza; stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); t_insert(oldstanza, stanza); t_insert(oldstanza.tags, stanza); end end function xml_handlers:CharacterData(data) if stanza then t_insert(chardata, data); end end function xml_handlers:EndElement(tagname) if non_streamns_depth > 0 then non_streamns_depth = non_streamns_depth - 1; end if stanza then if #chardata > 0 then -- We have some character data in the buffer t_insert(stanza, t_concat(chardata)); chardata = {}; end -- Complete stanza if #stack == 0 then if tagname ~= stream_error_tag then cb_handlestanza(session, stanza); else cb_error(session, "stream-error", stanza); end stanza = nil; else stanza = t_remove(stack); end else if cb_streamclosed then cb_streamclosed(session); end end end local function restricted_handler(parser) cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1."); if not parser.stop or not parser:stop() then error("Failed to abort parsing"); end end if lxp_supports_doctype then xml_handlers.StartDoctypeDecl = restricted_handler; end xml_handlers.Comment = restricted_handler; xml_handlers.ProcessingInstruction = restricted_handler; local function reset() stanza, chardata = nil, {}; stack = {}; end local function set_session(stream, new_session) session = new_session; end return xml_handlers, { reset = reset, set_session = set_session }; end function new(session, stream_callbacks) local handlers, meta = new_sax_handlers(session, stream_callbacks); local parser = new_parser(handlers, ns_separator); local parse = parser.parse; return { reset = function () parser = new_parser(handlers, ns_separator); parse = parser.parse; meta.reset(); end, feed = function (self, data) return parse(parser, data); end, set_session = meta.set_session; }; end return _M; prosody-0.9.1/util/openssl.lua0000644000175000017500000001044512213321667016276 0ustar matthewmatthewlocal type, tostring, pairs, ipairs = type, tostring, pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; local s_format = string.format; local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] local idna_to_ascii = require "util.encodings".idna.to_ascii; local _M = {}; local config = {}; _M.config = config; local ssl_config = {}; local ssl_config_mt = {__index=ssl_config}; function config.new() return setmetatable({ req = { distinguished_name = "distinguished_name", req_extensions = "v3_extensions", x509_extensions = "v3_extensions", prompt = "no", }, distinguished_name = { countryName = "GB", -- stateOrProvinceName = "", localityName = "The Internet", organizationName = "Your Organisation", organizationalUnitName = "XMPP Department", commonName = "example.com", emailAddress = "xmpp@example.com", }, v3_extensions = { basicConstraints = "CA:FALSE", keyUsage = "digitalSignature,keyEncipherment", extendedKeyUsage = "serverAuth,clientAuth", subjectAltName = "@subject_alternative_name", }, subject_alternative_name = { DNS = {}, otherName = {}, }, }, ssl_config_mt); end local DN_order = { "countryName"; "stateOrProvinceName"; "localityName"; "streetAddress"; "organizationName"; "organizationalUnitName"; "commonName"; "emailAddress"; } _M._DN_order = DN_order; function ssl_config:serialize() local s = ""; for k, t in pairs(self) do s = s .. ("[%s]\n"):format(k); if k == "subject_alternative_name" then for san, n in pairs(t) 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 local k = DN_order[i] local v = t[k]; if v then s = s .. ("%s = %s\n"):format(k, v); end end else for k, v in pairs(t) do s = s .. ("%s = %s\n"):format(k, v); end end s = s .. "\n"; end return s; end local function utf8string(s) -- This is how we tell openssl not to encode UTF-8 strings as fake Latin1 return s_format("FORMAT:UTF8,UTF8:%s", s); end local function ia5string(s) return s_format("IA5STRING:%s", s); end _M.util = { utf8string = utf8string, ia5string = ia5string, }; function ssl_config:add_dNSName(host) t_insert(self.subject_alternative_name.DNS, idna_to_ascii(host)); end 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)))); end function ssl_config:add_xmppAddr(host) t_insert(self.subject_alternative_name.otherName, s_format("%s;%s", oid_xmppaddr, utf8string(host))); end 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 local certhost = certhosts[i]; for name in pairs(hosts) do 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")); if config.get(name, "component_module") == nil then self:add_sRVName(name, "xmpp-client"); end --print(name .. "#anonymous_login: " .. tostring(config.get(name, "anonymous_login"))); if not (config.get(name, "anonymous_login") or config.get(name, "authentication") == "anonymous") then self:add_sRVName(name, "xmpp-server"); end self:add_xmppAddr(name); end end end if not found_matching_hosts then return nil, "no-matching-hosts"; end end do -- Lua to shell calls. local function shell_escape(s) return s:gsub("'",[['\'']]); end local function serialize(f,o) local r = {"openssl", f}; for k,v in pairs(o) do if type(k) == "string" then t_insert(r, ("-%s"):format(k)); if v ~= true then t_insert(r, ("'%s'"):format(shell_escape(tostring(v)))); end end end for _,v in ipairs(o) do t_insert(r, ("'%s'"):format(shell_escape(tostring(v)))); end return t_concat(r, " "); end local os_execute = os.execute; setmetatable(_M, { __index=function(_,f) return function(opts) return 0 == os_execute(serialize(f, type(opts) == "table" and opts or {})); end; end; }); end return _M; prosody-0.9.1/util/timer.lua0000644000175000017500000000347012213321667015733 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local server = require "net.server"; local math_min = math.min local math_huge = math.huge local get_time = require "socket".gettime; local t_insert = table.insert; local pairs = pairs; local type = type; local data = {}; local new_data = {}; module "timer" local _add_task; if not server.event then function _add_task(delay, callback) local current_time = get_time(); delay = delay + current_time; if delay >= current_time then t_insert(new_data, {delay, callback}); else local r = callback(current_time); if r and type(r) == "number" then return _add_task(r, callback); end end end server._addtimer(function() local current_time = get_time(); if #new_data > 0 then for _, d in pairs(new_data) do t_insert(data, d); end new_data = {}; end local next_time = math_huge; for i, d in pairs(data) do local t, callback = d[1], d[2]; if t <= current_time then data[i] = nil; local r = callback(current_time); if type(r) == "number" then _add_task(r, callback); next_time = math_min(next_time, r); end else next_time = math_min(next_time, t - current_time); end end return next_time; end); else local event = server.event; local event_base = server.event_base; local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1; function _add_task(delay, callback) local event_handle; event_handle = event_base:addevent(nil, 0, function () local ret = callback(get_time()); if ret then return 0, ret; elseif event_handle then return EVENT_LEAVE; end end , delay); end end add_task = _add_task; return _M; prosody-0.9.1/util/xml.lua0000644000175000017500000000243712213321667015415 0ustar matthewmatthew local st = require "util.stanza"; local lxp = require "lxp"; module("xml") local parse_xml = (function() local ns_prefixes = { ["http://www.w3.org/XML/1998/namespace"] = "xml"; }; local ns_separator = "\1"; local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; return function(xml) local handler = {}; local stanza = st.stanza("root"); function handler:StartElement(tagname, attr) local curr_ns,name = tagname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end if curr_ns ~= "" then attr.xmlns = curr_ns; end for i=1,#attr do local k = attr[i]; attr[i] = nil; local ns, nm = k:match(ns_pattern); if nm ~= "" then ns = ns_prefixes[ns]; if ns then attr[ns..":"..nm] = attr[k]; attr[k] = nil; end end end stanza:tag(name, attr); end function handler:CharacterData(data) stanza:text(data); end function handler:EndElement(tagname) stanza:up(); end local parser = lxp.new(handler, "\1"); local ok, err, line, col = parser:parse(xml); if ok then ok, err, line, col = parser:parse(); end --parser:close(); if ok then return stanza.tags[1]; else return ok, err.." (line "..line..", col "..col..")"; end end; end)(); parse = parse_xml; return _M; prosody-0.9.1/util/logger.lua0000644000175000017500000000320612213321667016067 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local pcall = pcall; local find = string.find; local ipairs, pairs, setmetatable = ipairs, pairs, setmetatable; module "logger" local level_sinks = {}; local make_logger; function init(name) local log_debug = make_logger(name, "debug"); local log_info = make_logger(name, "info"); local log_warn = make_logger(name, "warn"); local log_error = make_logger(name, "error"); return function (level, message, ...) if level == "debug" then return log_debug(message, ...); elseif level == "info" then return log_info(message, ...); elseif level == "warn" then return log_warn(message, ...); elseif level == "error" then return log_error(message, ...); end end end function make_logger(source_name, level) local level_handlers = level_sinks[level]; if not level_handlers then level_handlers = {}; level_sinks[level] = level_handlers; end local logger = function (message, ...) for i = 1,#level_handlers do level_handlers[i](source_name, level, message, ...); end end return logger; end function reset() for level, handler_list in pairs(level_sinks) do -- Clear all handlers for this level for i = 1, #handler_list do handler_list[i] = nil; end end end function add_level_sink(level, sink_function) if not level_sinks[level] then level_sinks[level] = { sink_function }; else level_sinks[level][#level_sinks[level] + 1 ] = sink_function; end end _M.new = make_logger; return _M; prosody-0.9.1/util/array.lua0000644000175000017500000000654312213321667015735 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert, t_sort, t_remove, t_concat = table.insert, table.sort, table.remove, table.concat; local setmetatable = setmetatable; local math_random = math.random; local pairs, ipairs = pairs, ipairs; local tostring = tostring; local array = {}; local array_base = {}; local array_methods = {}; local array_mt = { __index = array_methods, __tostring = function (array) return "{"..array:concat(", ").."}"; end }; local function new_array(self, t, _s, _var) if type(t) == "function" then -- Assume iterator t = self.collect(t, _s, _var); end return setmetatable(t or {}, array_mt); end function array_mt.__add(a1, a2) local res = new_array(); return res:append(a1):append(a2); end setmetatable(array, { __call = new_array }); -- Read-only methods function array_methods:random() return self[math_random(1,#self)]; end -- These methods can be called two ways: -- array.method(existing_array, [params [, ...]]) -- Create new array for result -- existing_array:method([params, ...]) -- Transform existing array into result -- function array_base.map(outa, ina, func) for k,v in ipairs(ina) do outa[k] = func(v); end return outa; end function array_base.filter(outa, ina, func) local inplace, start_length = ina == outa, #ina; local write = 1; for read=1,start_length do local v = ina[read]; if func(v) then outa[write] = v; write = write + 1; end end if inplace and write <= start_length then for i=write,start_length do outa[i] = nil; end end return outa; end function array_base.sort(outa, ina, ...) if ina ~= outa then outa:append(ina); end t_sort(outa, ...); return outa; end function array_base.pluck(outa, ina, key) for i=1,#ina do outa[i] = ina[i][key]; end return outa; end --- 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); self[i], self[r] = self[r], self[i]; end return self; end function array_methods:reverse() local len = #self-1; for i=len,1,-1 do self:push(self[i]); self:pop(i); end return self; end function array_methods:append(array) local len,len2 = #self, #array; for i=1,len2 do self[len+i] = array[i]; end return self; end function array_methods:push(x) t_insert(self, x); return self; end function array_methods:pop(x) local v = self[x]; t_remove(self, x); return v; end function array_methods:concat(sep) return t_concat(array.map(self, tostring), sep); end function array_methods:length() return #self; end --- These methods always create a new array function array.collect(f, s, var) local t = {}; while true do var = f(s, var); if var == nil then break; end t_insert(t, var); end return setmetatable(t, array_mt); end --- -- Setup methods from array_base for method, f in pairs(array_base) do local base_method = f; -- Setup global array method which makes new array array[method] = function (old_a, ...) local a = new_array(); return base_method(a, old_a, ...); end -- Setup per-array (mutating) method array_methods[method] = function (self, ...) return base_method(self, self, ...); end end _G.array = array; module("array"); return array; prosody-0.9.1/util/hmac.lua0000644000175000017500000000060712213321667015522 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- COMPAT: Only for external pre-0.9 modules local hashes = require "util.hashes" return { md5 = hashes.hmac_md5, sha1 = hashes.hmac_sha1, sha256 = hashes.hmac_sha256 }; prosody-0.9.1/util/uuid.lua0000644000175000017500000000236112213321667015557 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local m_random = math.random; local tostring = tostring; local os_time = os.time; local os_clock = os.clock; local sha1 = require "util.hashes".sha1; module "uuid" local last_uniq_time = 0; local function uniq_time() local new_uniq_time = os_time(); if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end last_uniq_time = new_uniq_time; return new_uniq_time; end local function new_random(x) return sha1(x..os_clock()..tostring({}), true); end local buffer = new_random(uniq_time()); local function _seed(x) buffer = new_random(buffer..x); end local function get_nibbles(n) if #buffer < n then _seed(uniq_time()); end local r = buffer:sub(0, n); buffer = buffer:sub(n+1); return r; end local function get_twobits() return ("%x"):format(get_nibbles(1):byte() % 4 + 8); end function generate() -- generate RFC 4122 complaint UUIDs (version 4 - random) return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12); end seed = _seed; return _M; prosody-0.9.1/util/pubsub.lua0000644000175000017500000002265512213321667016121 0ustar matthewmatthewlocal events = require "util.events"; module("pubsub", package.seeall); local service = {}; local service_mt = { __index = service }; local default_config = { broadcaster = function () end; get_affiliation = function () end; capabilities = {}; }; function new(config) config = config or {}; return setmetatable({ config = setmetatable(config, { __index = default_config }); affiliations = {}; subscriptions = {}; nodes = {}; events = events.new(); }, service_mt); end function service:jids_equal(jid1, jid2) local normalize = self.config.normalize_jid; return normalize(jid1) == normalize(jid2); end function service:may(node, actor, action) if actor == true then return true; end local node_obj = self.nodes[node]; local node_aff = node_obj and node_obj.affiliations[actor]; local service_aff = self.affiliations[actor] or self.config.get_affiliation(actor, node, action) or "none"; -- Check if node allows/forbids it local node_capabilities = node_obj and node_obj.capabilities; if node_capabilities then local caps = node_capabilities[node_aff or service_aff]; if caps then local can = caps[action]; if can ~= nil then return can; end end end -- Check service-wide capabilities instead local service_capabilities = self.config.capabilities; local caps = service_capabilities[node_aff or service_aff]; if caps then local can = caps[action]; if can ~= nil then return can; end end return false; end function service:set_affiliation(node, actor, jid, affiliation) -- Access checking if not self:may(node, actor, "set_affiliation") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end node_obj.affiliations[jid] = affiliation; local _, jid_sub = self:get_subscription(node, true, jid); if not jid_sub and not self:may(node, jid, "be_unsubscribed") then local ok, err = self:add_subscription(node, true, jid); if not ok then return ok, err; end elseif jid_sub and not self:may(node, jid, "be_subscribed") then local ok, err = self:add_subscription(node, true, jid); if not ok then return ok, err; end end return true; end function service:add_subscription(node, actor, jid, options) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "subscribe"; else cap = "subscribe_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end if not self:may(node, jid, "be_subscribed") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then if not self.config.autocreate_on_subscribe then return false, "item-not-found"; else local ok, err = self:create(node, true); if not ok then return ok, err; end node_obj = self.nodes[node]; end end node_obj.subscribers[jid] = options or true; local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then if not subs[jid] then subs[jid] = { [node] = true }; else subs[jid][node] = true; end else self.subscriptions[normal_jid] = { [jid] = { [node] = true } }; end self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options }); return true; end function service:remove_subscription(node, actor, jid) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "unsubscribe"; else cap = "unsubscribe_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end if not self:may(node, jid, "be_unsubscribed") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if not node_obj.subscribers[jid] then return false, "not-subscribed"; end node_obj.subscribers[jid] = nil; local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then local jid_subs = subs[jid]; if jid_subs then jid_subs[node] = nil; if next(jid_subs) == nil then subs[jid] = nil; end end if next(subs) == nil then self.subscriptions[normal_jid] = nil; end end self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid }); return true; end function service:remove_all_subscriptions(actor, jid) local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid] subs = subs and subs[jid]; if subs then for node in pairs(subs) do self:remove_subscription(node, true, jid); end end return true; end function service:get_subscription(node, actor, jid) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "get_subscription"; else cap = "get_subscription_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end return true, node_obj.subscribers[jid]; end function service:create(node, actor) -- Access checking if not self:may(node, actor, "create") then return false, "forbidden"; end -- if self.nodes[node] then return false, "conflict"; end self.nodes[node] = { name = node; subscribers = {}; config = {}; data = {}; affiliations = {}; }; local ok, err = self:set_affiliation(node, true, actor, "owner"); if not ok then self.nodes[node] = nil; end return ok, err; end function service:delete(node, actor) -- Access checking if not self:may(node, actor, "delete") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end self.nodes[node] = nil; self.config.broadcaster("delete", node, node_obj.subscribers); return true; end function service:publish(node, actor, id, item) -- Access checking if not self:may(node, actor, "publish") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then if not self.config.autocreate_on_publish then return false, "item-not-found"; end local ok, err = self:create(node, true); if not ok then return ok, err; end node_obj = self.nodes[node]; end node_obj.data[id] = item; self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item }); self.config.broadcaster("items", node, node_obj.subscribers, item); return true; end function service:retract(node, actor, id, retract) -- Access checking if not self:may(node, actor, "retract") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if (not node_obj) or (not node_obj.data[id]) then return false, "item-not-found"; end node_obj.data[id] = nil; if retract then self.config.broadcaster("items", node, node_obj.subscribers, retract); end return true end function service:purge(node, actor, notify) -- Access checking if not self:may(node, actor, "retract") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end node_obj.data = {}; -- Purge if notify then self.config.broadcaster("purge", node, node_obj.subscribers); end return true end function service:get_items(node, actor, id) -- Access checking if not self:may(node, actor, "get_items") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end if id then -- Restrict results to a single specific item return true, { [id] = node_obj.data[id] }; else return true, node_obj.data; end end function service:get_nodes(actor) -- Access checking if not self:may(nil, actor, "get_nodes") then return false, "forbidden"; end -- return true, self.nodes; end function service:get_subscriptions(node, actor, jid) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then cap = "get_subscriptions"; else cap = "get_subscriptions_other"; end if not self:may(node, actor, cap) then return false, "forbidden"; end -- local node_obj; if node then node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end end local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; -- We return the subscription object from the node to save -- a get_subscription() call for each node. local ret = {}; if subs then for jid, subscribed_nodes in pairs(subs) do if node then -- Return only subscriptions to this node if subscribed_nodes[node] then ret[#ret+1] = { node = node; jid = jid; subscription = node_obj.subscribers[jid]; }; end else -- Return subscriptions to all nodes local nodes = self.nodes; for subscribed_node in pairs(subscribed_nodes) do ret[#ret+1] = { node = subscribed_node; jid = jid; subscription = nodes[subscribed_node].subscribers[jid]; }; end end end end return true, ret; end -- Access models only affect 'none' affiliation caps, service/default access level... function service:set_node_capabilities(node, actor, capabilities) -- Access checking if not self:may(node, actor, "configure") then return false, "forbidden"; end -- local node_obj = self.nodes[node]; if not node_obj then return false, "item-not-found"; end node_obj.capabilities = capabilities; return true; end return _M; prosody-0.9.1/util/sql.lua0000644000175000017500000002301312213321667015405 0ustar matthewmatthew 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; prosody-0.9.1/util/datamanager.lua0000644000175000017500000002571212213321667017062 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format = string.format; local setmetatable = setmetatable; local ipairs = ipairs; local char = string.char; local pcall = pcall; local log = require "util.logger".init("datamanager"); local io_open = io.open; local os_remove = os.remove; local os_rename = os.rename; local tonumber = tonumber; local next = next; local t_insert = table.insert; local t_concat = table.concat; local envloadfile = require"util.envload".envloadfile; local serialize = require "util.serialization".serialize; local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua) local lfs = require "lfs"; local prosody = prosody; local raw_mkdir = lfs.mkdir; local function fallocate(f, offset, len) -- This assumes that current position == offset local fake_data = (" "):rep(len); local ok, msg = f:write(fake_data); if not ok then return ok, msg; end f:seek("set", offset); return true; end; pcall(function() local pposix = require "util.pposix"; raw_mkdir = pposix.mkdir or raw_mkdir; -- Doesn't trample on umask fallocate = pposix.fallocate or fallocate; end); module "datamanager" ---- utils ----- local encode, decode; do local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); decode = function (s) return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); end encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end end local _mkdir = {}; local function mkdir(path) path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here if not _mkdir[path] then raw_mkdir(path); _mkdir[path] = true; end return path; end local data_path = (prosody and prosody.paths and prosody.paths.data) or "."; local callbacks = {}; ------- API ------------- function set_data_path(path) log("debug", "Setting data path to: %s", path); data_path = path; end local function callback(username, host, datastore, data) for _, f in ipairs(callbacks) do username, host, datastore, data = f(username, host, datastore, data); if username == false then break; end end return username, host, datastore, data; end function add_callback(func) if not callbacks[func] then -- Would you really want to set the same callback more than once? callbacks[func] = true; callbacks[#callbacks+1] = func; return true; end end function remove_callback(func) if callbacks[func] then for i, f in ipairs(callbacks) do if f == func then callbacks[i] = nil; callbacks[f] = nil; return true; end end end end function getpath(username, host, datastore, ext, create) ext = ext or "dat"; host = (host and encode(host)) or "_global"; username = username and encode(username); if username then if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end return format("%s/%s/%s/%s.%s", data_path, host, datastore, username, ext); else if create then mkdir(mkdir(data_path).."/"..host); end return format("%s/%s/%s.%s", data_path, host, datastore, ext); end end function load(username, host, datastore) local data, ret = envloadfile(getpath(username, host, datastore), {}); if not data then local mode = lfs.attributes(getpath(username, host, datastore), "mode"); if not mode then log("debug", "Assuming empty %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil; else -- file exists, but can't be read -- TODO more detailed error checking and logging? log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end end local success, ret = pcall(data); if not success then log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end return ret; end local function atomic_store(filename, data) local scratch = filename.."~"; local f, ok, msg; repeat f, msg = io_open(scratch, "w"); if not f then break end ok, msg = f:write(data); if not ok then break end ok, msg = f:close(); if not ok then break end return os_rename(scratch, filename); until false; -- Cleanup if f then f:close(); end os_remove(scratch); return nil, msg; end if prosody.platform ~= "posix" then -- os.rename does not overwrite existing files on Windows -- TODO We could use Transactional NTFS on Vista and above function atomic_store(filename, data) local f, err = io_open(filename, "w"); if not f then return f, err; end local ok, msg = f:write(data); if not ok then f:close(); return ok, msg; end return f:close(); end end function store(username, host, datastore, data) if not data then data = {}; end username, host, datastore, data = callback(username, host, datastore, data); if username == false then return true; -- Don't save this data at all end -- save the datastore local d = "return " .. serialize(data) .. ";\n"; 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 function list_append(username, host, datastore, data) if not data then return; end if callback(username, host, datastore) == false then return true; end -- save the datastore local f, msg = io_open(getpath(username, host, datastore, "list", true), "r+"); if not f then f, msg = io_open(getpath(username, host, datastore, "list", true), "w"); end if not f then log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil"); return; end local data = "item(" .. serialize(data) .. ");\n"; local pos = f:seek("end"); local ok, msg = fallocate(f, pos, #data); f:seek("set", pos); if ok then f:write(data); else log("error", "Unable to write to %s storage ('%s') for user: %s@%s", datastore, msg, username or "nil", host or "nil"); return ok, msg; end f:close(); return true; end function list_store(username, host, datastore, data) if not data then data = {}; end if callback(username, host, datastore) == false then return true; end -- save the datastore local d = {}; for _, item in ipairs(data) do d[#d+1] = "item(" .. serialize(item) .. ");\n"; end local ok, msg = atomic_store(getpath(username, host, datastore, "list", true), t_concat(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; 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, "list")); end -- we write data even when we are deleting because lua doesn't have a -- platform independent way of checking for non-exisitng files return true; end function list_load(username, host, datastore) local items = {}; local data, ret = envloadfile(getpath(username, host, datastore, "list"), {item = function(i) t_insert(items, i); end}); if not data then local mode = lfs.attributes(getpath(username, host, datastore, "list"), "mode"); if not mode then log("debug", "Assuming empty %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil; else -- file exists, but can't be read -- TODO more detailed error checking and logging? log("error", "Failed to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end end local success, ret = pcall(data); if not success then log("error", "Unable to load %s storage ('%s') for user: %s@%s", datastore, ret, username or "nil", host or "nil"); return nil, "Error reading storage"; end return items; end local type_map = { keyval = "dat"; list = "list"; } function users(host, store, typ) typ = type_map[typ or "keyval"]; local store_dir = format("%s/%s/%s", data_path, encode(host), store); local mode, err = lfs.attributes(store_dir, "mode"); if not mode then return function() log("debug", err or (store_dir .. " does not exist")) end end local next, state = lfs.dir(store_dir); return function(state) for node in next, state do local file, ext = node:match("^(.*)%.([dalist]+)$"); if file and ext == typ then return decode(file); end end end, state; end function stores(username, host, typ) typ = type_map[typ or "keyval"]; local store_dir = format("%s/%s/", data_path, encode(host)); local mode, err = lfs.attributes(store_dir, "mode"); if not mode then return function() log("debug", err or (store_dir .. " does not exist")) end end local next, state = lfs.dir(store_dir); return function(state) for node in next, state do if not node:match"^%." then if username == true then if lfs.attributes(store_dir..node, "mode") == "directory" then return decode(node); end elseif username then local store = decode(node) if lfs.attributes(getpath(username, host, store, typ), "mode") then return store; end elseif lfs.attributes(node, "mode") == "file" then local file, ext = node:match("^(.*)%.([dalist]+)$"); if ext == typ then return decode(file) end end end end end, state; end local function do_remove(path) local ok, err = os_remove(path); if not ok and lfs.attributes(path, "mode") then return ok, err; end return true end function purge(username, host) local host_dir = format("%s/%s/", data_path, encode(host)); local errs = {}; for file in lfs.dir(host_dir) do if lfs.attributes(host_dir..file, "mode") == "directory" then local store = decode(file); local ok, err = do_remove(getpath(username, host, store)); if not ok then errs[#errs+1] = err; end local ok, err = do_remove(getpath(username, host, store, "list")); if not ok then errs[#errs+1] = err; end end end return #errs == 0, t_concat(errs, ", "); end _M.path_decode = decode; _M.path_encode = encode; return _M; prosody-0.9.1/util/watchdog.lua0000644000175000017500000000143412213321667016411 0ustar matthewmatthewlocal timer = require "util.timer"; local setmetatable = setmetatable; local os_time = os.time; module "watchdog" local watchdog_methods = {}; local watchdog_mt = { __index = watchdog_methods }; function new(timeout, callback) local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt); timer.add_task(timeout+1, function (current_time) local last_reset = watchdog.last_reset; if not last_reset then return; end local time_left = (last_reset + timeout) - current_time; if time_left < 0 then return watchdog:callback(); end return time_left + 1; end); return watchdog; end function watchdog_methods:reset() self.last_reset = os_time(); end function watchdog_methods:cancel() self.last_reset = nil; end return _M; prosody-0.9.1/util/envload.lua0000644000175000017500000000144112213321667016237 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2011 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local load, loadstring, loadfile, setfenv = load, loadstring, loadfile, setfenv; local envload; local envloadfile; if setfenv then function envload(code, source, env) local f, err = loadstring(code, source); if f and env then setfenv(f, env); end return f, err; end function envloadfile(file, env) local f, err = loadfile(file); if f and env then setfenv(f, env); end return f, err; end else function envload(code, source, env) return load(code, source, nil, env); end function envloadfile(file, env) return loadfile(file, nil, env); end end return { envload = envload, envloadfile = envloadfile }; prosody-0.9.1/util/filters.lua0000644000175000017500000000346212213321667016264 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local t_insert, t_remove = table.insert, table.remove; module "filters" local new_filter_hooks = {}; function initialize(session) if not session.filters then local filters = {}; session.filters = filters; function session.filter(type, data) local filter_list = filters[type]; if filter_list then for i = 1, #filter_list do data = filter_list[i](data, session); if data == nil then break; end end end return data; end end for i=1,#new_filter_hooks do new_filter_hooks[i](session); end return session.filter; end function add_filter(session, type, callback, priority) if not session.filters then initialize(session); end local filter_list = session.filters[type]; if not filter_list then filter_list = {}; session.filters[type] = filter_list; end priority = priority or 0; local i = 0; repeat i = i + 1; until not filter_list[i] or filter_list[filter_list[i]] >= priority; t_insert(filter_list, i, callback); filter_list[callback] = priority; end function remove_filter(session, type, callback) if not session.filters then return; end local filter_list = session.filters[type]; if filter_list and filter_list[callback] then for i=1, #filter_list do if filter_list[i] == callback then t_remove(filter_list, i); filter_list[callback] = nil; return true; end end end end function add_filter_hook(callback) t_insert(new_filter_hooks, callback); end function remove_filter_hook(callback) for i=1,#new_filter_hooks do if new_filter_hooks[i] == callback then t_remove(new_filter_hooks, i); end end end return _M; prosody-0.9.1/util/sasl/0000775000175000017500000000000012213321667015050 5ustar matthewmatthewprosody-0.9.1/util/sasl/digest-md5.lua0000644000175000017500000002220612213321667017515 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local tostring = tostring; local type = type; local s_gmatch = string.gmatch; local s_match = string.match; local t_concat = table.concat; local t_insert = table.insert; local to_byte, to_char = string.byte, string.char; local md5 = require "util.hashes".md5; local log = require "util.logger".init("sasl"); local generate_uuid = require "util.uuid".generate; local nodeprep = require "util.encodings".stringprep.nodeprep; module "sasl.digest-md5" --========================= --SASL DIGEST-MD5 according to RFC 2831 --[[ Supported Authentication Backends digest_md5: function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken -- implementations it's not return digesthash, state; end digest_md5_test: function(username, domain, realm, encoding, digesthash) return true or false, state; end ]] local function digest(self, message) --TODO complete support for authzid local function serialize(message) local data = "" -- testing all possible values if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end if message["charset"] then data = data..[[charset=]]..message.charset.."," end if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end data = data:gsub(",$", "") return data end local function utf8tolatin1ifpossible(passwd) local i = 1; while i <= #passwd do local passwd_i = to_byte(passwd:sub(i, i)); if passwd_i > 0x7F then if passwd_i < 0xC0 or passwd_i > 0xC3 then return passwd; end i = i + 1; passwd_i = to_byte(passwd:sub(i, i)); if passwd_i < 0x80 or passwd_i > 0xBF then return passwd; end end i = i + 1; end local p = {}; local j = 0; i = 1; while (i <= #passwd) do local passwd_i = to_byte(passwd:sub(i, i)); if passwd_i > 0x7F then i = i + 1; local passwd_i_1 = to_byte(passwd:sub(i, i)); t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever else t_insert(p, to_char(passwd_i)); end i = i + 1; end return t_concat(p); end local function latin1toutf8(str) local p = {}; for ch in s_gmatch(str, ".") do ch = to_byte(ch); if (ch < 0x80) then t_insert(p, to_char(ch)); elseif (ch < 0xC0) then t_insert(p, to_char(0xC2, ch)); else t_insert(p, to_char(0xC3, ch - 64)); end end return t_concat(p); end local function parse(data) local message = {} -- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0") for k, v in s_gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder message[k] = v; end return message; end if not self.nonce then self.nonce = generate_uuid(); self.step = 0; self.nonce_count = {}; end self.step = self.step + 1; if (self.step == 1) then local challenge = serialize({ nonce = self.nonce, qop = "auth", charset = "utf-8", algorithm = "md5-sess", realm = self.realm}); return "challenge", challenge; elseif (self.step == 2) then local response = parse(message); -- check for replay attack if response["nc"] then if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end end -- check for username, it's REQUIRED by RFC 2831 local username = response["username"]; local _nodeprep = self.profile.nodeprep; if username and _nodeprep ~= false then username = (_nodeprep or nodeprep)(username); -- FIXME charset end if not username or username == "" then return "failure", "malformed-request"; end self.username = username; -- check for nonce, ... if not response["nonce"] then return "failure", "malformed-request"; else -- check if it's the right nonce if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end end if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end if not response["qop"] then response["qop"] = "auth" end if response["realm"] == nil or response["realm"] == "" then response["realm"] = ""; elseif response["realm"] ~= self.realm then return "failure", "not-authorized", "Incorrect realm value"; end local decoder; if response["charset"] == nil then decoder = utf8tolatin1ifpossible; elseif response["charset"] ~= "utf-8" then return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; end local domain = ""; local protocol = ""; if response["digest-uri"] then protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); if protocol == nil or domain == nil then return "failure", "malformed-request" end else return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." end --TODO maybe realm support local Y, state; if self.profile.plain then local password, state = self.profile.plain(self, response["username"], self.realm) if state == nil then return "failure", "not-authorized" elseif state == false then return "failure", "account-disabled" end Y = md5(response["username"]..":"..response["realm"]..":"..password); elseif self.profile["digest-md5"] then Y, state = self.profile["digest-md5"](self, response["username"], self.realm, response["realm"], response["charset"]) if state == nil then return "failure", "not-authorized" elseif state == false then return "failure", "account-disabled" end elseif self.profile["digest-md5-test"] then -- TODO end --local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); --if Y == nil then return "failure", "not-authorized" --elseif Y == false then return "failure", "account-disabled" end local A1 = ""; if response.authzid then if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then -- COMPAT log("warn", "Client is violating RFC 3920 (section 6.1, point 7)."); A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; else return "failure", "invalid-authzid"; end else A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; end local A2 = "AUTHENTICATE:"..protocol.."/"..domain; local HA1 = md5(A1, true); local HA2 = md5(A2, true); local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; local response_value = md5(KD, true); if response_value == response["response"] then -- calculate rspauth A2 = ":"..protocol.."/"..domain; HA1 = md5(A1, true); HA2 = md5(A2, true); KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 local rspauth = md5(KD, true); self.authenticated = true; --TODO: considering sending the rspauth in a success node for saving one roundtrip; allowed according to http://tools.ietf.org/html/draft-saintandre-rfc3920bis-09#section-7.3.6 return "challenge", serialize({rspauth = rspauth}); else return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." end elseif self.step == 3 then if self.authenticated ~= nil then return "success" else return "failure", "malformed-request" end end end function init(registerMechanism) registerMechanism("DIGEST-MD5", {"plain"}, digest); end return _M; prosody-0.9.1/util/sasl/plain.lua0000644000175000017500000000715212213321667016661 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local log = require "util.logger".init("sasl"); module "sasl.plain" -- ================================ -- SASL PLAIN according to RFC 4616 --[[ Supported Authentication Backends plain: function(username, realm) return password, state; end plain_test: function(username, password, realm) return true or false, state; end ]] local function plain(self, message) if not message then return "failure", "malformed-request"; end local authorization, authentication, password = s_match(message, "^([^%z]*)%z([^%z]+)%z([^%z]+)"); if not authorization then return "failure", "malformed-request"; end -- SASLprep password and authentication authentication = saslprep(authentication); password = saslprep(password); if (not password) or (password == "") or (not authentication) or (authentication == "") then log("debug", "Username or password violates SASLprep."); return "failure", "malformed-request", "Invalid username or password."; end local _nodeprep = self.profile.nodeprep; if _nodeprep ~= false then authentication = (_nodeprep or nodeprep)(authentication); if not authentication or authentication == "" then return "failure", "malformed-request", "Invalid username or password." end end local correct, state = false, false; if self.profile.plain then local correct_password; correct_password, state = self.profile.plain(self, authentication, self.realm); correct = (correct_password == password); elseif self.profile.plain_test then correct, state = self.profile.plain_test(self, authentication, password, self.realm); end self.username = authentication if state == false then return "failure", "account-disabled"; elseif state == nil or not correct then return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent."; end return "success"; end function init(registerMechanism) registerMechanism("PLAIN", {"plain", "plain_test"}, plain); end return _M; prosody-0.9.1/util/sasl/anonymous.lua0000644000175000017500000000436712213321667017613 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local log = require "util.logger".init("sasl"); local generate_uuid = require "util.uuid".generate; module "sasl.anonymous" --========================= --SASL ANONYMOUS according to RFC 4505 --[[ Supported Authentication Backends anonymous: function(username, realm) return true; --for normal usage just return true; if you don't like the supplied username you can return false. end ]] local function anonymous(self, message) local username; repeat username = generate_uuid(); until self.profile.anonymous(self, username, self.realm); self.username = username; return "success" end function init(registerMechanism) registerMechanism("ANONYMOUS", {"anonymous"}, anonymous); end return _M; prosody-0.9.1/util/sasl/scram.lua0000644000175000017500000002243712213321667016666 0ustar matthewmatthew-- sasl.lua v0.4 -- Copyright (C) 2008-2010 Tobias Markmann -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local s_match = string.match; local type = type local string = string local base64 = require "util.encodings".base64; local hmac_sha1 = require "util.hashes".hmac_sha1; local sha1 = require "util.hashes".sha1; local Hi = require "util.hashes".scram_Hi_sha1; local generate_uuid = require "util.uuid".generate; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local log = require "util.logger".init("sasl"); local t_concat = table.concat; local char = string.char; local byte = string.byte; module "sasl.scram" --========================= --SASL SCRAM-SHA-1 according to RFC 5802 --[[ Supported Authentication Backends scram_{MECH}: -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_' function(username, realm) return stored_key, server_key, iteration_count, salt, state; end ]] local default_i = 4096 local function bp( b ) local result = "" for i=1, b:len() do result = result.."\\"..b:byte(i) end return result end local xor_map = {0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;1;0;3;2;5;4;7;6;9;8;11;10;13;12;15;14;2;3;0;1;6;7;4;5;10;11;8;9;14;15;12;13;3;2;1;0;7;6;5;4;11;10;9;8;15;14;13;12;4;5;6;7;0;1;2;3;12;13;14;15;8;9;10;11;5;4;7;6;1;0;3;2;13;12;15;14;9;8;11;10;6;7;4;5;2;3;0;1;14;15;12;13;10;11;8;9;7;6;5;4;3;2;1;0;15;14;13;12;11;10;9;8;8;9;10;11;12;13;14;15;0;1;2;3;4;5;6;7;9;8;11;10;13;12;15;14;1;0;3;2;5;4;7;6;10;11;8;9;14;15;12;13;2;3;0;1;6;7;4;5;11;10;9;8;15;14;13;12;3;2;1;0;7;6;5;4;12;13;14;15;8;9;10;11;4;5;6;7;0;1;2;3;13;12;15;14;9;8;11;10;5;4;7;6;1;0;3;2;14;15;12;13;10;11;8;9;6;7;4;5;2;3;0;1;15;14;13;12;11;10;9;8;7;6;5;4;3;2;1;0;}; local result = {}; local function binaryXOR( a, b ) for i=1, #a do local x, y = byte(a, i), byte(b, i); local lowx, lowy = x % 16, y % 16; local hix, hiy = (x - lowx) / 16, (y - lowy) / 16; local lowr, hir = xor_map[lowx * 16 + lowy + 1], xor_map[hix * 16 + hiy + 1]; local r = hir * 16 + lowr; result[i] = char(r) end return t_concat(result); end local function validate_username(username, _nodeprep) -- check for forbidden char sequences for eq in username:gmatch("=(.?.?)") do if eq ~= "2C" and eq ~= "3D" then return false end end -- replace =2C with , and =3D with = username = username:gsub("=2C", ","); username = username:gsub("=3D", "="); -- apply SASLprep username = saslprep(username); if username and _nodeprep ~= false then username = (_nodeprep or nodeprep)(username); end return username and #username>0 and username; end local function hashprep(hashname) return hashname:lower():gsub("-", "_"); end function getAuthenticationDatabaseSHA1(password, salt, iteration_count) if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then return false, "inappropriate argument types" end if iteration_count < 4096 then log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.") end local salted_password = Hi(password, salt, iteration_count); local stored_key = sha1(hmac_sha1(salted_password, "Client Key")) local server_key = hmac_sha1(salted_password, "Server Key"); return true, stored_key, server_key end local function scram_gen(hash_name, H_f, HMAC_f) local function scram_hash(self, message) if not self.state then self["state"] = {} end if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end if not self.state.name then -- we are processing client_first_message local client_first_message = message; -- TODO: fail if authzid is provided, since we don't support them yet self.state["client_first_message"] = client_first_message; self.state["gs2_cbind_flag"], self.state["authzid"], self.state["name"], self.state["clientnonce"] = client_first_message:match("^(%a),(.*),n=(.*),r=([^,]*).*"); -- we don't do any channel binding yet if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then return "failure", "malformed-request"; end if not self.state.name or not self.state.clientnonce then return "failure", "malformed-request", "Channel binding isn't support at this time."; end self.state.name = validate_username(self.state.name, self.profile.nodeprep); if not self.state.name then log("debug", "Username violates either SASLprep or contains forbidden character sequences.") return "failure", "malformed-request", "Invalid username."; end self.state["servernonce"] = generate_uuid(); -- retreive credentials if self.profile.plain then local password, state = self.profile.plain(self, self.state.name, self.realm) if state == nil then return "failure", "not-authorized" elseif state == false then return "failure", "account-disabled" end password = saslprep(password); if not password then log("debug", "Password violates SASLprep."); return "failure", "not-authorized", "Invalid password." end self.state.salt = generate_uuid(); self.state.iteration_count = default_i; local succ = false; succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count); if not succ then log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key); return "failure", "temporary-auth-failure"; end elseif self.profile["scram_"..hashprep(hash_name)] then local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self, self.state.name, self.realm); if state == nil then return "failure", "not-authorized" elseif state == false then return "failure", "account-disabled" end self.state.stored_key = stored_key; self.state.server_key = server_key; self.state.iteration_count = iteration_count; self.state.salt = salt end local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count; self.state["server_first_message"] = server_first_message; return "challenge", server_first_message else -- we are processing client_final_message local client_final_message = message; self.state["channelbinding"], self.state["nonce"], self.state["proof"] = client_final_message:match("^c=(.*),r=(.*),.*p=(.*)"); if not self.state.proof or not self.state.nonce or not self.state.channelbinding then return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; end if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then return "failure", "malformed-request", "Wrong nonce in client-final-message."; end local ServerKey = self.state.server_key; local StoredKey = self.state.stored_key; local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") local ClientSignature = HMAC_f(StoredKey, AuthMessage) local ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof)) local ServerSignature = HMAC_f(ServerKey, AuthMessage) if StoredKey == H_f(ClientKey) then local server_final_message = "v="..base64.encode(ServerSignature); self["username"] = self.state.name; return "success", server_final_message; else return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; end end end return scram_hash; end function init(registerMechanism) local function registerSCRAMMechanism(hash_name, hash, hmac_hash) registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash)); end registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); end return _M; prosody-0.9.1/util/x509.lua0000644000175000017500000001467012213321667015324 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2010 Matthew Wild -- Copyright (C) 2010 Paul Aurich -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- TODO: I feel a fair amount of this logic should be integrated into Luasec, -- so that everyone isn't re-inventing the wheel. Dependencies on -- IDN libraries complicate that. -- [TLS-CERTS] - http://tools.ietf.org/html/rfc6125 -- [XMPP-CORE] - http://tools.ietf.org/html/rfc6120 -- [SRV-ID] - http://tools.ietf.org/html/rfc4985 -- [IDNA] - http://tools.ietf.org/html/rfc5890 -- [LDAP] - http://tools.ietf.org/html/rfc4519 -- [PKIX] - http://tools.ietf.org/html/rfc5280 local nameprep = require "util.encodings".stringprep.nameprep; local idna_to_ascii = require "util.encodings".idna.to_ascii; 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" local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3 local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] -- Compare a hostname (possibly international) with asserted names -- extracted from a certificate. -- This function follows the rules laid out in -- sections 6.4.1 and 6.4.2 of [TLS-CERTS] -- -- A wildcard ("*") all by itself is allowed only as the left-most label local function compare_dnsname(host, asserted_names) -- TODO: Sufficient normalization? Review relevant specs. local norm_host = idna_to_ascii(host) if norm_host == nil then log("info", "Host %s failed IDNA ToASCII operation", host) return false end norm_host = norm_host:lower() local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label for i=1,#asserted_names do local name = asserted_names[i] if norm_host == name:lower() then log("debug", "Cert dNSName %s matched hostname", name); return true end -- Allow the left most label to be a "*" if name:match("^%*%.") then local rest_name = name:gsub("^[^.]+%.", "") if host_chopped == rest_name:lower() then log("debug", "Cert dNSName %s matched hostname", name); return true end end end return false end -- Compare an XMPP domain name with the asserted id-on-xmppAddr -- identities extracted from a certificate. Both are UTF8 strings. -- -- Per [XMPP-CORE], matches against asserted identities don't include -- wildcards, so we just do a normalize on both and then a string comparison -- -- TODO: Support for full JIDs? local function compare_xmppaddr(host, asserted_names) local norm_host = nameprep(host) for i=1,#asserted_names do local name = asserted_names[i] -- We only want to match against bare domains right now, not -- those crazy full-er JIDs. if name:match("[@/]") then log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name) else local norm_name = nameprep(name) if norm_name == nil then log("info", "Ignoring xmppAddr %s, failed nameprep!", name) else if norm_host == norm_name then log("debug", "Cert xmppAddr %s matched hostname", name) return true end end end end return false end -- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID) -- identities extracted from a certificate. -- -- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII. -- Comparison is done case-insensitively, and a wildcard ("*") all by itself -- is allowed only as the left-most non-service label. local function compare_srvname(host, service, asserted_names) local norm_host = idna_to_ascii(host) if norm_host == nil then log("info", "Host %s failed IDNA ToASCII operation", host); return false end -- Service names start with a "_" if service:match("^_") == nil then service = "_"..service end norm_host = norm_host:lower(); local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label for i=1,#asserted_names do local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)"); if service == asserted_service then if norm_host == name:lower() then log("debug", "Cert SRVName %s matched hostname", name); return true; end -- Allow the left most label to be a "*" if name:match("^%*%.") then local rest_name = name:gsub("^[^.]+%.", "") if host_chopped == rest_name:lower() then log("debug", "Cert SRVName %s matched hostname", name) return true end end if norm_host == name:lower() then log("debug", "Cert SRVName %s matched hostname", name); return true end end end return false end function verify_identity(host, service, cert) local ext = cert:extensions() if ext[oid_subjectaltname] then local sans = ext[oid_subjectaltname]; -- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a -- reference identifier if the presented identifiers include a DNS-ID -- SRV-ID, URI-ID, or any application-specific identifier types" local had_supported_altnames = false if sans[oid_xmppaddr] then had_supported_altnames = true if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end end if sans[oid_dnssrv] then had_supported_altnames = true -- Only check srvNames if the caller specified a service if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end end if sans["dNSName"] then had_supported_altnames = true if compare_dnsname(host, sans["dNSName"]) then return true end end -- We don't need URIs, but [TLS-CERTS] is clear. if sans["uniformResourceIdentifier"] then had_supported_altnames = true end if had_supported_altnames then return false end end -- Extract a common name from the certificate, and check it as if it were -- a dNSName subjectAltName (wildcards may apply for, and receive, -- cat treats) -- -- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject -- which has one and only one Common Name local subject = cert:subject() local cn = nil for i=1,#subject do local dn = subject[i] if dn["oid"] == oid_commonname then if cn then log("info", "Certificate has multiple common names") return false end cn = dn["value"]; end end if cn then -- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs. return compare_dnsname(host, { cn }) end -- If all else fails, well, why should we be any different? return false end return _M; prosody-0.9.1/util/jid.lua0000644000175000017500000000533512213321667015363 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local match = string.match; local nodeprep = require "util.encodings".stringprep.nodeprep; local nameprep = require "util.encodings".stringprep.nameprep; local resourceprep = require "util.encodings".stringprep.resourceprep; local escapes = { [" "] = "\\20"; ['"'] = "\\22"; ["&"] = "\\26"; ["'"] = "\\27"; ["/"] = "\\2f"; [":"] = "\\3a"; ["<"] = "\\3c"; [">"] = "\\3e"; ["@"] = "\\40"; ["\\"] = "\\5c"; }; local unescapes = {}; for k,v in pairs(escapes) do unescapes[v] = k; end module "jid" local function _split(jid) if not jid then return; end local node, nodepos = match(jid, "^([^@/]+)@()"); local host, hostpos = match(jid, "^([^@/]+)()", nodepos) if node and not host then return nil, nil, nil; end local resource = match(jid, "^/(.+)$", hostpos); if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end return node, host, resource; end split = _split; function bare(jid) local node, host = _split(jid); if node and host then return node.."@"..host; end return host; end local function _prepped_split(jid) local node, host, resource = _split(jid); if host then host = nameprep(host); if not host then return; end if node then node = nodeprep(node); if not node then return; end end if resource then resource = resourceprep(resource); if not resource then return; end end return node, host, resource; end end prepped_split = _prepped_split; function prep(jid) local node, host, resource = _prepped_split(jid); if host then if node then host = node .. "@" .. host; end if resource then host = host .. "/" .. resource; end end return host; end function join(node, host, resource) if node and host and resource then return node.."@"..host.."/"..resource; elseif node and host then return node.."@"..host; elseif host and resource then return host.."/"..resource; elseif host then return host; end return nil; -- Invalid JID end function compare(jid, acl) -- compare jid to single acl rule -- TODO compare to table of rules? local jid_node, jid_host, jid_resource = _split(jid); local acl_node, acl_host, acl_resource = _split(acl); if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then return true end return false end function escape(s) return s and (s:gsub(".", escapes)); end function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end return _M; prosody-0.9.1/TODO0000644000175000017500000000011212213321667013611 0ustar matthewmatthew== 1.0 == - Roster providers - Statistics - Clustering - World domination prosody-0.9.1/README0000644000175000017500000000212012213321667014002 0ustar matthewmatthew# Prosody IM Server ## Description Prosody is a server for Jabber/XMPP written in Lua. It aims to be easy to use and light on resources. For developers, it aims to give a flexible system on which to rapidly develop added functionality or rapidly prototype new protocols. ## Useful links Homepage: http://prosody.im/ Download: http://prosody.im/download Documentation: http://prosody.im/doc/ Jabber/XMPP Chat: Address: prosody@conference.prosody.im Web interface: http://prosody.im/webchat Mailing lists: User support and discussion: http://groups.google.com/group/prosody-users Development discussion: http://groups.google.com/group/prosody-dev Issue tracker changes: http://groups.google.com/group/prosody-issues ## Installation See the accompanying INSTALL file for help on building Prosody from source. Alternatively see our guide at http://prosody.im/doc/install prosody-0.9.1/tools/0000775000175000017500000000000012213321667014271 5ustar matthewmatthewprosody-0.9.1/tools/erlparse.lua0000644000175000017500000001065012213321667016611 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local string_byte, string_char = string.byte, string.char; local t_concat, t_insert = table.concat, table.insert; local type, tonumber, tostring = type, tonumber, tostring; local file = nil; local last = nil; local line = 1; local function read(expected) local ch; if last then ch = last; last = nil; else ch = file:read(1); if ch == "\n" then line = line + 1; end end if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end return ch; end local function pushback(ch) if last then error(); end last = ch; end local function peek() if not last then last = read(); end return last; end local _A, _a, _Z, _z, _0, _9, __, _at, _space, _minus = string_byte("AaZz09@_ -", 1, 10); local function isLowerAlpha(ch) ch = string_byte(ch) or 0; return (ch >= _a and ch <= _z); end local function isNumeric(ch) ch = string_byte(ch) or 0; return (ch >= _0 and ch <= _9) or ch == _minus; end local function isAtom(ch) ch = string_byte(ch) or 0; return (ch >= _A and ch <= _Z) or (ch >= _a and ch <= _z) or (ch >= _0 and ch <= _9) or ch == __ or ch == _at; end local function isSpace(ch) ch = string_byte(ch) or "x"; return ch <= _space; end local escapes = {["\\b"]="\b", ["\\d"]="\127", ["\\e"]="\27", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]=" ", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"}; local function readString() read("\""); -- skip quote local slash = nil; local str = {}; while true do local ch = read(); if slash then slash = slash..ch; if not escapes[slash] then error("Unknown escape sequence: "..slash); end str[#str+1] = escapes[slash]; slash = nil; elseif ch == "\"" then break; elseif ch == "\\" then slash = ch; else str[#str+1] = ch; end end return t_concat(str); end local function readAtom1() local var = { read() }; while isAtom(peek()) do var[#var+1] = read(); end return t_concat(var); end local function readAtom2() local str = { read("'") }; local slash = nil; while true do local ch = read(); str[#str+1] = ch; if ch == "'" and not slash then break; end end return t_concat(str); end local function readNumber() local num = { read() }; while isNumeric(peek()) do num[#num+1] = read(); end if peek() == "." then num[#num+1] = read(); while isNumeric(peek()) do num[#num+1] = read(); end end return tonumber(t_concat(num)); end local readItem = nil; local function readTuple() local t = {}; local s = {}; -- string representation read(); -- read {, or [, or < while true do local item = readItem(); if not item then break; end if type(item) ~= "number" or item > 255 then s = nil; elseif s then s[#s+1] = string_char(item); end t_insert(t, item); end read(); -- read }, or ], or > if s and #s > 0 then return t_concat(s) else return t end; end local function readBinary() read("<"); -- read < -- Discard PIDs if isNumeric(peek()) then while peek() ~= ">" do read(); end read(">"); return {}; end local t = readTuple(); read(">") -- read > local ch = peek(); if type(t) == "string" then -- binary is a list of integers return t; elseif type(t) == "table" then if t[1] then -- binary contains string return t[1]; else -- binary is empty return ""; end; else error(); end end readItem = function() local ch = peek(); if ch == nil then return nil end if ch == "{" or ch == "[" then return readTuple(); elseif isLowerAlpha(ch) then return readAtom1(); elseif ch == "'" then return readAtom2(); elseif isNumeric(ch) then return readNumber(); elseif ch == "\"" then return readString(); elseif ch == "<" then return readBinary(); elseif isSpace(ch) or ch == "," or ch == "|" then read(); return readItem(); else --print("Unknown char: "..ch); return nil; end end local function readChunk() local x = readItem(); if x then read("."); end return x; end local function readFile(filename) file = io.open(filename); if not file then error("File not found: "..filename); os.exit(0); end return function() local x = readChunk(); if not x and peek() then error("Invalid char: "..peek()); end return x; end; end module "erlparse" function parseFile(file) return readFile(file); end return _M; prosody-0.9.1/tools/openfire2prosody.lua0000644000175000017500000000564712213321667020317 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2009 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- package.path = package.path..";../?.lua"; package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager -- ugly workaround for getting datamanager to work outside of prosody :( prosody = { }; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "posix"; end local parse_xml = require "util.xml".parse; ----------------------------------------------------------------------- package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager" dm.set_data_path("data"); local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[Openfire importer for Prosody Usage: openfire2prosody.lua filename.xml hostname ]]); os.exit(1); end local host = select(2, ...) or "localhost"; local file = assert(io.open(arg)); local data = assert(file:read("*a")); file:close(); local xml = assert(parse_xml(data)); assert(xml.name == "Openfire", "The input file is not an Openfire XML export"); local substatus_mapping = { ["0"] = "none", ["1"] = "to", ["2"] = "from", ["3"] = "both" }; for _,tag in ipairs(xml.tags) do if tag.name == "User" then local username, password, roster; for _,tag in ipairs(tag.tags) do if tag.name == "Username" then username = tag:get_text(); elseif tag.name == "Password" then password = tag:get_text(); elseif tag.name == "Roster" then roster = {}; local pending = {}; for _,tag in ipairs(tag.tags) do if tag.name == "Item" then local jid = assert(tag.attr.jid, "Roster item has no JID"); if tag.attr.substatus ~= "-1" then local item = {}; item.name = tag.attr.name; item.subscription = assert(substatus_mapping[tag.attr.substatus], "invalid substatus"); item.ask = tag.attr.askstatus == "0" and "subscribe" or nil; local groups = {}; for _,tag in ipairs(tag) do if tag.name == "Group" then groups[tag:get_text()] = true; end end item.groups = groups; roster[jid] = item; end if tag.attr.recvstatus == "1" then pending[jid] = true; end end end if next(pending) then roster[false] = { pending = pending }; end end end assert(username and password, "No username or password"); local ret, err = dm.store(username, host, "accounts", {password = password}); print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password); if roster then local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored roster: "..username.."@"..host.." = "..password); end end end prosody-0.9.1/tools/ejabberd2prosody.lua0000755000175000017500000002262412213321667020243 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- package.path = package.path ..";../?.lua"; if arg[0]:match("[/\\]") then package.path = package.path .. ";"..arg[0]:gsub("[^/\\]*$", "?.lua"); end local erlparse = require "erlparse"; prosody = {}; package.loaded["util.logger"] = {init = function() return function() end; end} local serialize = require "util.serialization".serialize; local st = require "util.stanza"; local dm = require "util.datamanager" dm.set_data_path("data"); function build_stanza(tuple, stanza) assert(type(tuple) == "table", "XML node is of unexpected type: "..type(tuple)); if tuple[1] == "xmlelement" then assert(type(tuple[2]) == "string", "element name has type: "..type(tuple[2])); assert(type(tuple[3]) == "table", "element attribute array has type: "..type(tuple[3])); assert(type(tuple[4]) == "table", "element children array has type: "..type(tuple[4])); local name = tuple[2]; local attr = {}; for _, a in ipairs(tuple[3]) do if type(a[1]) == "string" and type(a[2]) == "string" then attr[a[1]] = a[2]; end end local up; if stanza then stanza:tag(name, attr); up = true; else stanza = st.stanza(name, attr); end for _, a in ipairs(tuple[4]) do build_stanza(a, stanza); end if up then stanza:up(); else return stanza end elseif tuple[1] == "xmlcdata" then assert(type(tuple[2]) == "string", "XML CDATA has unexpected type: "..type(tuple[2])); stanza:text(tuple[2]); else error("unknown element type: "..serialize(tuple)); end end function build_time(tuple) local Megaseconds,Seconds,Microseconds = unpack(tuple); return Megaseconds * 1000000 + Seconds; end function vcard(node, host, stanza) local ret, err = dm.store(node, host, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] vCard: "..node.."@"..host); end function password(node, host, password) local ret, err = dm.store(node, host, "accounts", {password = password}); print("["..(err or "success").."] accounts: "..node.."@"..host); end function roster(node, host, jid, item) local roster = dm.load(node, host, "roster") or {}; roster[jid] = item; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function roster_pending(node, host, jid) local roster = dm.load(node, host, "roster") or {}; roster.pending = roster.pending or {}; roster.pending[jid] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function private_storage(node, host, xmlns, stanza) local private = dm.load(node, host, "private") or {}; private[stanza.name..":"..xmlns] = st.preserialize(stanza); local ret, err = dm.store(node, host, "private", private); print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns); end function offline_msg(node, host, t, stanza) stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t); stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t); local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t)); end function privacy(node, host, default, lists) local privacy = { lists = {} }; local count = 0; if default then privacy.default = default; end for _, inlist in ipairs(lists) do local name, items = inlist[1], inlist[2]; local list = { name = name; items = {}; }; local orders = {}; for _, item in pairs(items) do repeat if item[1] ~= "listitem" then print("[error] privacy: unhandled item: "..tostring(item[1])); break; end local _type, value = item[2], item[3]; if _type == "jid" then if type(value) ~= "table" then print("[error] privacy: jid value is not valid: "..tostring(value)); break; end local _node, _host, _resource = value[1], value[2], value[3]; if (type(_node) == "table") then _node = nil; end if (type(_host) == "table") then _host = nil; end if (type(_resource) == "table") then _resource = nil; end value = (_node and _node.."@".._host or _host)..(_resource and "/".._resource or ""); elseif _type == "none" then _type = nil; value = nil; elseif _type == "group" then if type(value) ~= "string" then print("[error] privacy: group value is not string: "..tostring(value)); break; end elseif _type == "subscription" then if value~="both" and value~="from" and value~="to" and value~="none" then print("[error] privacy: subscription value is invalid: "..tostring(value)); break; end else print("[error] privacy: invalid item type: "..tostring(_type)); break; end local action = item[4]; if action ~= "allow" and action ~= "deny" then print("[error] privacy: unhandled action: "..tostring(action)); break; end local order = item[5]; if type(order) ~= "number" or order<0 then print("[error] privacy: order is not numeric: "..tostring(order)); break; end if orders[order] then print("[error] privacy: duplicate order value: "..tostring(order)); break; end orders[order] = true; local match_all = item[6]; local match_iq = item[7]; local match_message = item[8]; local match_presence_in = item[9]; local match_presence_out = item[10]; list.items[#list.items+1] = { type = _type; value = value; action = action; order = order; message = match_message == "true"; iq = match_iq == "true"; ["presence-in"] = match_presence_in == "true"; ["presence-out"] = match_presence_out == "true"; }; until true; end table.sort(list.items, function(a, b) return a.order < b.order; end); if privacy.lists[list.name] then print("[warn] duplicate privacy list: "..tostring(list.name)); end privacy.lists[list.name] = list; count = count + 1; end if default and not privacy.lists[default] then if default == "none" then privacy.default = nil; else print("[warn] default privacy list doesn't exist: "..tostring(default)); end end local ret, err = dm.store(node, host, "privacy", privacy); print("["..(err or "success").."] privacy: " ..node.."@"..host.." - "..count.." list(s)"); end local filters = { passwd = function(tuple) password(tuple[2][1], tuple[2][2], tuple[3]); end; vcard = function(tuple) vcard(tuple[2][1], tuple[2][2], build_stanza(tuple[3])); end; roster = function(tuple) local node = tuple[3][1]; local host = tuple[3][2]; local contact = (type(tuple[4][1]) == "table") and tuple[4][2] or tuple[4][1].."@"..tuple[4][2]; local name = tuple[5]; local subscription = tuple[6]; local ask = tuple[7]; local groups = tuple[8]; if type(name) ~= type("") then name = nil; end if ask == "none" then ask = nil; elseif ask == "out" then ask = "subscribe" elseif ask == "in" then roster_pending(node, host, contact); ask = nil; elseif ask == "both" then roster_pending(node, host, contact); ask = "subscribe"; else error("Unknown ask type: "..ask); end if subscription ~= "both" and subscription ~= "from" and subscription ~= "to" and subscription ~= "none" then error(subscription) end local item = {name = name, ask = ask, subscription = subscription, groups = {}}; for _, g in ipairs(groups) do if type(g) == "string" then item.groups[g] = true; end end roster(node, host, contact, item); end; private_storage = function(tuple) private_storage(tuple[2][1], tuple[2][2], tuple[2][3], build_stanza(tuple[3])); end; offline_msg = function(tuple) offline_msg(tuple[2][1], tuple[2][2], build_time(tuple[3]), build_stanza(tuple[7])); end; privacy = function(tuple) privacy(tuple[2][1], tuple[2][2], tuple[3], tuple[4]); end; config = function(tuple) if tuple[2] == "hosts" then local output = io.output(); io.output("prosody.cfg.lua"); io.write("-- Configuration imported from ejabberd --\n"); io.write([[Host "*" modules_enabled = { "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. "legacyauth"; -- Legacy authentication. Only used by some old clients and bots. "roster"; -- Allow users to have a roster. Recommended ;) "register"; -- Allow users to register on this server using a client "tls"; -- Add support for secure TLS on c2s/s2s connections "vcard"; -- Allow users to set vCards "private"; -- Private XML storage (for room bookmarks, etc.) "version"; -- Replies to server version requests "dialback"; -- s2s dialback support "uptime"; "disco"; "time"; "ping"; --"selftests"; }; ]]); for _, h in ipairs(tuple[3]) do io.write("Host \"" .. h .. "\"\n"); end io.output(output); print("prosody.cfg.lua created"); end end; }; local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[ejabberd db dump importer for Prosody Usage: ejabberd2prosody.lua filename.txt The file can be generated from ejabberd using: sudo ./bin/ejabberdctl dump filename.txt Note: The path of ejabberdctl depends on your ejabberd installation, and ejabberd needs to be running for ejabberdctl to work.]]); os.exit(1); end local count = 0; local t = {}; for item in erlparse.parseFile(arg) do count = count + 1; local name = item[1]; t[name] = (t[name] or 0) + 1; --print(count, serialize(item)); if filters[name] then filters[name](item); end end --print(serialize(t)); prosody-0.9.1/tools/migration/0000775000175000017500000000000012213321667016262 5ustar matthewmatthewprosody-0.9.1/tools/migration/migrator.cfg.lua0000644000175000017500000000053312213321667021346 0ustar matthewmatthewlocal data_path = "../../data"; input { type = "prosody_files"; path = data_path; } output { type = "prosody_sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } --[[ input { type = "prosody_files"; path = data_path; } output { type = "prosody_sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } ]] prosody-0.9.1/tools/migration/migrator/0000775000175000017500000000000012213321667020106 5ustar matthewmatthewprosody-0.9.1/tools/migration/migrator/jabberd14.lua0000644000175000017500000001061212213321667022345 0ustar matthewmatthew local lfs = require "lfs"; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; local os_getenv = os.getenv; local io_open = io.open; local assert = assert; local ipairs = ipairs; local coroutine = coroutine; local print = print; module "jabberd14" local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end local function is_file(path) return lfs.attributes(path, "mode") == "file"; end local function clean_path(path) return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~"); end local function load_xml(path) local f, err = io_open(path); if not f then return f, err; end local data = f:read("*a"); f:close(); if not data then return; end return parse_xml(data); end local function load_spool_file(host, filename, path) local xml = load_xml(path); if not xml then return; end local register_element = xml:get_child("query", "jabber:iq:register"); local username_element = register_element and register_element:get_child("username", "jabber:iq:register"); local password_element = register_element and register_element:get_child("password", "jabber:iq:auth"); local username = username_element and username_element:get_text(); local password = password_element and password_element:get_text(); if not username then print("[warn] Missing /xdb/{jabber:iq:register}register/username> in file "..filename) return; elseif username..".xml" ~= filename then print("[warn] Missing /xdb/{jabber:iq:register}register/username does not match filename "..filename); return; end local userdata = { user = username; host = host; stores = {}; }; local stores = userdata.stores; stores.accounts = { password = password }; for i=1,#xml.tags do local tag = xml.tags[i]; local xname = (tag.attr.xmlns or "")..":"..tag.name; if tag.attr.j_private_flag == "1" and tag.attr.xmlns then -- Private XML stores.private = stores.private or {}; tag.attr.j_private_flag = nil; stores.private[tag.attr.xmlns] = st.preserialize(tag); elseif xname == "jabber:iq:auth:password" then if stores.accounts.password ~= tag:get_text() then if password then print("[warn] conflicting passwords") else stores.accounts.password = tag:get_text(); end end elseif xname == "jabber:iq:register:query" then -- already processed elseif xname == "jabber:xdb:nslist:foo" then -- ignore elseif xname == "jabber:iq:auth:0k:zerok" then -- ignore elseif xname == "jabber:iq:roster:query" then -- Roster local roster = {}; local subscription_types = { from = true, to = true, both = true, none = true }; for _,item_element in ipairs(tag.tags) do assert(item_element.name == "item"); assert(item_element.attr.jid); assert(subscription_types[item_element.attr.subscription]); assert((item_element.attr.ask or "subscribe") == "subscribe") if item_element.name == "item" then local groups = {}; for _,group_element in ipairs(item_element.tags) do assert(group_element.name == "group"); groups[group_element:get_text()] = true; end local item = { name = item_element.attr.name; subscription = item_element.attr.subscription; ask = item_element.attr.ask; groups = groups; }; roster[item_element.attr.jid] = item; end end stores.roster = roster; elseif xname == "jabber:iq:last:query" then -- Last activity elseif xname == "jabber:x:offline:foo" then -- Offline messages elseif xname == "vcard-temp:vCard" then -- vCards stores.vcard = st.preserialize(tag); else print("[warn] Unknown tag: "..xname); end end return userdata; end local function loop_over_users(path, host, cb) for file in lfs.dir(path) do if file:match("%.xml$") then local user = load_spool_file(host, file, path.."/"..file); if user then cb(user); end end end end local function loop_over_hosts(path, cb) for host in lfs.dir(path) do if host ~= "." and host ~= ".." and is_dir(path.."/"..host) then loop_over_users(path.."/"..host, host, cb); end end end function reader(input) local path = clean_path(assert(input.path, "no input.path specified")); assert(is_dir(path), "input.path is not a directory"); if input.host then return coroutine.wrap(function() loop_over_users(input.path, input.host, coroutine.yield) end); else return coroutine.wrap(function() loop_over_hosts(input.path, coroutine.yield) end); end end return _M; prosody-0.9.1/tools/migration/migrator/prosody_files.lua0000644000175000017500000001063612213321667023476 0ustar matthewmatthew local print = print; local assert = assert; local setmetatable = setmetatable; local tonumber = tonumber; local char = string.char; local coroutine = coroutine; local lfs = require "lfs"; local loadfile = loadfile; local pcall = pcall; local mtools = require "migrator.mtools"; local next = next; local pairs = pairs; local json = require "util.json"; local os_getenv = os.getenv; prosody = {}; local dm = require "util.datamanager" module "prosody_files" local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end local function is_file(path) return lfs.attributes(path, "mode") == "file"; end local function clean_path(path) return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~"); end local encode, decode; do local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); decode = function (s) return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); end encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end end local function decode_dir(x) if x:gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then return decode(x); end end local function decode_file(x) if x:match(".%.dat$") and x:gsub("%.dat$", ""):gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then return decode(x:gsub("%.dat$", "")); end end local function prosody_dir(path, ondir, onfile, ...) for x in lfs.dir(path) do local xpath = path.."/"..x; if decode_dir(x) and is_dir(xpath) then ondir(xpath, x, ...); elseif decode_file(x) and is_file(xpath) then onfile(xpath, x, ...); end end end local function handle_root_file(path, name) --print("root file: ", decode_file(name)) coroutine.yield { user = nil, host = nil, store = decode_file(name) }; end local function handle_host_file(path, name, host) --print("host file: ", decode_dir(host).."/"..decode_file(name)) coroutine.yield { user = nil, host = decode_dir(host), store = decode_file(name) }; end local function handle_store_file(path, name, store, host) --print("store file: ", decode_file(name).."@"..decode_dir(host).."/"..decode_dir(store)) coroutine.yield { user = decode_file(name), host = decode_dir(host), store = decode_dir(store) }; end local function handle_host_store(path, name, host) prosody_dir(path, function() end, handle_store_file, name, host); end local function handle_host_dir(path, name) prosody_dir(path, handle_host_store, handle_host_file, name); end local function handle_root_dir(path) prosody_dir(path, handle_host_dir, handle_root_file); end local function decode_user(item) local userdata = { user = item[1].user; host = item[1].host; stores = {}; }; for i=1,#item do -- loop over stores local result = {}; local store = item[i]; userdata.stores[store.store] = store.data; store.user = nil; store.host = nil; store.store = nil; end return userdata; end function reader(input) local path = clean_path(assert(input.path, "no input.path specified")); assert(is_dir(path), "input.path is not a directory"); local iter = coroutine.wrap(function()handle_root_dir(path);end); -- get per-user stores, sorted local iter = mtools.sorted { reader = function() local x = iter(); if x then dm.set_data_path(path); local err; x.data, err = dm.load(x.user, x.host, x.store); if x.data == nil and err then error(("Error loading data at path %s for %s@%s (%s store)") :format(path, x.user or "", x.host or "", x.store or ""), 0); end return x; end end; sorter = function(a, b) local a_host, a_user, a_store = a.host or "", a.user or "", a.store or ""; local b_host, b_user, b_store = b.host or "", b.user or "", b.store or ""; return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store); end; }; -- merge stores to get users iter = mtools.merged(iter, function(a, b) return (a.host == b.host and a.user == b.user); end); return function() local x = iter(); return x and decode_user(x); end end function writer(output) local path = clean_path(assert(output.path, "no output.path specified")); assert(is_dir(path), "output.path is not a directory"); return function(item) if not item then return; end -- end of input dm.set_data_path(path); for store, data in pairs(item.stores) do assert(dm.store(item.user, item.host, store, data)); end end end return _M; prosody-0.9.1/tools/migration/migrator/mtools.lua0000644000175000017500000000201512213321667022122 0ustar matthewmatthew local print = print; local t_insert = table.insert; local t_sort = table.sort; module "mtools" function sorted(params) local reader = params.reader; -- iterator to get items from local sorter = params.sorter; -- sorting function local filter = params.filter; -- filter function local cache = {}; for item in reader do if filter then item = filter(item); end if item then t_insert(cache, item); end end if sorter then t_sort(cache, sorter); end local i = 0; return function() i = i + 1; return cache[i]; end; end function merged(reader, merger) local item1 = reader(); local merged = { item1 }; return function() while true do if not item1 then return nil; end local item2 = reader(); if not item2 then item1 = nil; return merged; end if merger(item1, item2) then --print("merged") item1 = item2; t_insert(merged, item1); else --print("unmerged", merged) item1 = item2; local tmp = merged; merged = { item1 }; return tmp; end end end; end return _M; prosody-0.9.1/tools/migration/migrator/prosody_sql.lua0000644000175000017500000001432012213321667023165 0ustar matthewmatthew local assert = assert; local have_DBI, DBI = pcall(require,"DBI"); local print = print; local type = type; local next = next; local pairs = pairs; local t_sort = table.sort; local json = require "util.json"; local mtools = require "migrator.mtools"; local tostring = tostring; local tonumber = tonumber; if not have_DBI then error("LuaDBI (required for SQL support) was not found, please see http://prosody.im/doc/depends#luadbi", 0); end module "prosody_sql" local function create_table(connection, params) 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"); end local stmt = connection:prepare(create_sql); if stmt then local ok = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then 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 stmt, err = connection:prepare(index_sql); local ok, commit_ok, commit_err; if stmt then ok, err = assert(stmt:execute()); commit_ok, commit_err = assert(connection:commit()); end elseif params.driver == "MySQL" then -- COMPAT: Upgrade tables from 0.8.0 -- Failed to create, but check existing MySQL table here local stmt = connection:prepare("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); local ok = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then if stmt:rowcount() > 0 then local stmt = connection:prepare("ALTER TABLE prosody MODIFY COLUMN `value` MEDIUMTEXT"); local ok = stmt:execute(); local commit_ok = connection:commit(); if ok and commit_ok then print("Database table automatically upgraded"); end end repeat until not stmt:fetch(); end end end 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 function decode_user(item) local userdata = { user = item[1][1].user; host = item[1][1].host; stores = {}; }; for i=1,#item do -- loop over stores local result = {}; local store = item[i]; for i=1,#store do -- loop over store data local row = store[i]; local k = row.key; local v = deserialize(row.type, row.value); 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 userdata.stores[store[1].store] = result; end end return userdata; end function reader(input) local dbh = assert(DBI.Connect( assert(input.driver, "no input.driver specified"), assert(input.database, "no input.database specified"), input.username, input.password, input.host, input.port )); assert(dbh:ping()); local stmt = assert(dbh:prepare("SELECT * FROM prosody")); assert(stmt:execute()); local keys = {"host", "user", "store", "key", "type", "value"}; local f,s,val = stmt:rows(true); -- get SQL rows, sorted local iter = mtools.sorted { reader = function() val = f(s, val); return val; end; filter = function(x) for i=1,#keys do if not x[keys[i]] then return false; end -- TODO log error, missing field end if x.host == "" then x.host = nil; end if x.user == "" then x.user = nil; end if x.store == "" then x.store = nil; end return x; end; sorter = function(a, b) local a_host, a_user, a_store = a.host or "", a.user or "", a.store or ""; local b_host, b_user, b_store = b.host or "", b.user or "", b.store or ""; return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store); end; }; -- merge rows to get stores iter = mtools.merged(iter, function(a, b) return (a.host == b.host and a.user == b.user and a.store == b.store); end); -- merge stores to get users iter = mtools.merged(iter, function(a, b) return (a[1].host == b[1].host and a[1].user == b[1].user); end); return function() local x = iter(); return x and decode_user(x); end; end function writer(output, iter) local dbh = assert(DBI.Connect( assert(output.driver, "no output.driver specified"), assert(output.database, "no output.database specified"), output.username, output.password, output.host, output.port )); assert(dbh:ping()); create_table(dbh, output); local stmt = assert(dbh:prepare("SELECT * FROM prosody")); assert(stmt:execute()); local stmt = assert(dbh:prepare("DELETE FROM prosody")); assert(stmt:execute()); local insert_sql = "INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)"; if output.driver == "PostgreSQL" then insert_sql = insert_sql:gsub("`", "\""); end local insert = assert(dbh:prepare(insert_sql)); return function(item) if not item then assert(dbh:commit()) return dbh:close(); end -- end of input local host = item.host or ""; local user = item.user or ""; for store, data in pairs(item.stores) do -- TODO transactions local extradata = {}; for key, value in pairs(data) do if type(key) == "string" and key ~= "" then local t, value = assert(serialize(value)); local ok, err = assert(insert:execute(host, user, store, key, t, value)); else extradata[key] = value; end end if next(extradata) ~= nil then local t, extradata = assert(serialize(extradata)); local ok, err = assert(insert:execute(host, user, store, "", t, extradata)); end end end; end return _M; prosody-0.9.1/tools/migration/prosody-migrator.lua0000644000175000017500000000670612213321667022315 0ustar matthewmatthew#!/usr/bin/env lua CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); -- Substitute ~ with path to home directory in paths if CFG_CONFIGDIR then CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME")); end if CFG_SOURCEDIR then CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME")); end local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; -- Command-line parsing local options = {}; local handled_opts = 0; for i = 1, #arg do if arg[i]:sub(1,2) == "--" then local opt, val = arg[i]:match("([%w-]+)=?(.*)"); if opt then options[(opt:sub(3):gsub("%-", "_"))] = #val > 0 and val or true; end handled_opts = i; else break; end end table.remove(arg, handled_opts); if CFG_SOURCEDIR then package.path = CFG_SOURCEDIR.."/?.lua;"..package.path; package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath; else package.path = "../../?.lua;"..package.path package.cpath = "../../?.so;"..package.cpath end local envloadfile = require "util.envload".envloadfile; -- Load config file local function loadfilein(file, env) if loadin then return loadin(env, io.open(file):read("*a")); else return envloadfile(file, env); end end local config_file = options.config or default_config; local from_store = arg[1] or "input"; local to_store = arg[2] or "output"; config = {}; local config_env = setmetatable({}, { __index = function(t, k) return function(tbl) config[k] = tbl; end; end }); local config_chunk, err = loadfilein(config_file, config_env); if not config_chunk then print("There was an error loading the config file, check the file exists"); print("and that the syntax is correct:"); print("", err); os.exit(1); end config_chunk(); local have_err; if #arg > 0 and #arg ~= 2 then have_err = true; print("Error: Incorrect number of parameters supplied."); end if not config[from_store] then have_err = true; print("Error: Input store '"..from_store.."' not found in the config file."); end if not config[to_store] then have_err = true; print("Error: Output store '"..to_store.."' not found in the config file."); end function load_store_handler(name) local store_type = config[name].type; if not store_type then print("Error: "..name.." store type not specified in the config file"); return false; else local ok, err = pcall(require, "migrator."..store_type); if not ok then if package.loaded["migrator."..store_type] then print(("Error: Failed to initialize '%s' store:\n\t%s") :format(name, err)); else print(("Error: Unrecognised store type for '%s': %s") :format(from_store, store_type)); end return false; end end return true; end have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output")); if have_err then print(""); print("Usage: "..arg[0].." FROM_STORE TO_STORE"); print("If no stores are specified, 'input' and 'output' are used."); print(""); print("The available stores in your migrator config are:"); print(""); for store in pairs(config) do print("", store); end print(""); os.exit(1); end local itype = config[from_store].type; local otype = config[to_store].type; local reader = require("migrator."..itype).reader(config[from_store]); local writer = require("migrator."..otype).writer(config[to_store]); local json = require "util.json"; io.stderr:write("Migrating...\n"); for x in reader do --print(json.encode(x)) writer(x); end writer(nil); -- close io.stderr:write("Done!\n"); prosody-0.9.1/tools/migration/Makefile0000644000175000017500000000252712213321667017726 0ustar matthewmatthew include ../../config.unix BIN = $(DESTDIR)$(PREFIX)/bin CONFIG = $(DESTDIR)$(SYSCONFDIR) SOURCE = $(DESTDIR)$(PREFIX)/lib/prosody DATA = $(DESTDIR)$(DATADIR) MAN = $(DESTDIR)$(PREFIX)/share/man INSTALLEDSOURCE = $(PREFIX)/lib/prosody INSTALLEDCONFIG = $(SYSCONFDIR) INSTALLEDMODULES = $(PREFIX)/lib/prosody/modules INSTALLEDDATA = $(DATADIR) SOURCE_FILES = migrator/*.lua all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua $(SOURCE_FILES) install: prosody-migrator.install migrator.cfg.lua.install install -d $(BIN) $(CONFIG) $(SOURCE) $(SOURCE)/migrator install -d $(MAN)/man1 install -d $(SOURCE)/migrator install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator install -m644 $(SOURCE_FILES) $(SOURCE)/migrator test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua clean: rm -f prosody-migrator.install rm -f migrator.cfg.lua.install prosody-migrator.install: prosody-migrator.lua sed "1s/\blua\b/$(RUNWITH)/; \ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|;" \ < prosody-migrator.lua > prosody-migrator.install migrator.cfg.lua.install: migrator.cfg.lua sed "s|^local data_path = .*;$$|local data_path = '$(INSTALLEDDATA)';|;" \ < migrator.cfg.lua > migrator.cfg.lua.install prosody-0.9.1/tools/jabberd14sql2prosody.lua0000644000175000017500000007521112213321667020760 0ustar matthewmatthew#!/usr/bin/env lua do local _parse_sql_actions = { [0] = 0, 1, 0, 1, 1, 2, 0, 2, 2, 0, 9, 2, 0, 10, 2, 0, 11, 2, 0, 13, 2, 1, 2, 2, 1, 6, 3, 0, 3, 4, 3, 0, 3, 5, 3, 0, 3, 7, 3, 0, 3, 8, 3, 0, 3, 12, 4, 0, 2, 3, 7, 4, 0, 3, 8, 11 }; local _parse_sql_trans_keys = { [0] = 0, 0, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 82, 82, 69, 69, 65, 65, 84, 84, 69, 69, 32, 32, 68, 84, 65, 65, 84, 84, 65, 65, 66, 66, 65, 65, 83, 83, 69, 69, 9, 47, 9, 96, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 10, 96, 10, 96, 9, 47, 9, 59, 45, 45, 10, 10, 42, 42, 10, 42, 10, 47, 65, 65, 66, 66, 76, 76, 69, 69, 32, 32, 73, 96, 70, 70, 32, 32, 78, 78, 79, 79, 84, 84, 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 40, 10, 10, 32, 41, 32, 32, 75, 96, 69, 69, 89, 89, 32, 32, 96, 96, 10, 96, 10, 96, 10, 10, 82, 82, 73, 73, 77, 77, 65, 65, 82, 82, 89, 89, 32, 32, 75, 75, 69, 69, 89, 89, 32, 32, 78, 78, 73, 73, 81, 81, 85, 85, 69, 69, 32, 32, 75, 75, 10, 96, 10, 96, 10, 10, 10, 59, 10, 59, 82, 82, 79, 79, 80, 80, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 32, 32, 73, 73, 70, 70, 32, 32, 69, 69, 88, 88, 73, 73, 83, 83, 84, 84, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 59, 59, 78, 78, 83, 83, 69, 69, 82, 82, 84, 84, 32, 32, 73, 73, 78, 78, 84, 84, 79, 79, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 40, 86, 10, 41, 32, 32, 86, 86, 65, 65, 76, 76, 85, 85, 69, 69, 83, 83, 32, 32, 40, 40, 39, 78, 10, 92, 10, 92, 41, 44, 44, 59, 32, 78, 48, 57, 41, 57, 48, 57, 41, 57, 85, 85, 76, 76, 76, 76, 34, 116, 79, 79, 67, 67, 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 83, 83, 32, 32, 96, 96, 10, 96, 10, 96, 32, 32, 87, 87, 82, 82, 73, 73, 84, 84, 69, 69, 69, 69, 84, 84, 32, 32, 10, 59, 10, 59, 78, 83, 76, 76, 79, 79, 67, 67, 75, 75, 32, 32, 84, 84, 65, 65, 66, 66, 76, 76, 69, 69, 83, 83, 69, 69, 9, 85, 0 }; local _parse_sql_key_spans = { [0] = 0, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 39, 88, 1, 1, 1, 33, 38, 87, 87, 39, 51, 1, 1, 1, 33, 38, 1, 1, 1, 1, 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 10, 1, 22, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 50, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 47, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 40, 83, 83, 4, 16, 47, 10, 17, 10, 17, 1, 1, 1, 83, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 87, 87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 50, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 77 }; local _parse_sql_index_offsets = { [0] = 0, 0, 2, 4, 6, 40, 79, 81, 83, 85, 87, 89, 91, 109, 111, 113, 115, 117, 119, 121, 123, 163, 252, 254, 256, 258, 292, 331, 419, 507, 547, 599, 601, 603, 605, 639, 678, 680, 682, 684, 686, 688, 713, 715, 717, 719, 721, 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, 829, 917, 919, 921, 923, 934, 936, 959, 961, 963, 965, 967, 1055, 1143, 1145, 1147, 1149, 1151, 1153, 1155, 1157, 1159, 1161, 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1269, 1357, 1359, 1410, 1461, 1463, 1465, 1467, 1469, 1471, 1473, 1475, 1477, 1479, 1481, 1483, 1485, 1487, 1489, 1491, 1493, 1495, 1497, 1499, 1501, 1503, 1591, 1679, 1681, 1683, 1685, 1687, 1689, 1691, 1693, 1695, 1697, 1699, 1701, 1703, 1705, 1793, 1881, 1883, 1931, 1964, 1966, 1968, 1970, 1972, 1974, 1976, 1978, 1980, 1982, 2023, 2107, 2191, 2196, 2213, 2261, 2272, 2290, 2301, 2319, 2321, 2323, 2325, 2409, 2411, 2413, 2415, 2417, 2419, 2421, 2423, 2425, 2427, 2429, 2431, 2433, 2521, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623, 2625, 2627, 2678, 2729, 2736, 2738, 2740, 2742, 2744, 2746, 2748, 2750, 2752, 2754, 2756, 2758, 2760 }; local _parse_sql_indicies = { [0] = 0, 1, 2, 0, 3, 1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 6, 3, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20, 1, 21, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 1, 25, 1, 22, 23, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 27, 1, 23, 27, 28, 1, 29, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 29, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 22, 28, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 1, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 31, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36, 1, 37, 1, 34, 35, 34, 34, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36, 1, 37, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 38, 1, 35, 38, 39, 1, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 41, 39, 39, 39, 39, 34, 39, 42, 1, 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52, 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 48, 1, 63, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 1, 62, 65, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, 64, 67, 1, 68, 1, 69, 1, 70, 1, 1, 1, 1, 1, 1, 1, 1, 71, 1, 72, 1, 73, 1, 1, 1, 1, 74, 1, 1, 1, 1, 75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77, 1, 78, 1, 79, 1, 80, 1, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 1, 81, 82, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 83, 81, 69, 83, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91, 1, 92, 1, 93, 1, 83, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 73, 1, 101, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1, 100, 103, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 104, 102, 105, 83, 106, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 108, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 107, 71, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118, 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128, 1, 129, 1, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 1, 130, 131, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 132, 130, 6, 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142, 1, 143, 1, 144, 1, 146, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 1, 145, 148, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 149, 147, 150, 1, 151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 152, 1, 153, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 154, 151, 155, 1, 152, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1, 162, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166, 1, 168, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 169, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 170, 167, 172, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 173, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 174, 171, 175, 1, 1, 176, 1, 161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 177, 1, 178, 1, 1, 1, 1, 1, 1, 163, 1, 1, 1, 1, 1, 164, 1, 1, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 166, 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 180, 1, 1, 181, 1, 182, 1, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 180, 1, 1, 181, 1, 1, 1, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 1, 184, 1, 185, 1, 186, 1, 171, 1, 1, 171, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 171, 1, 1, 171, 1, 1, 171, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 171, 1, 1, 1, 171, 1, 171, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192, 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 1, 199, 200, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 201, 199, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 132, 1, 207, 1, 208, 1, 209, 1, 210, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 2, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 211, 209, 212, 1, 1, 1, 1, 213, 1, 214, 1, 215, 1, 216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 132, 1, 127, 1, 6, 2, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 224, 1, 225, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 226, 227, 1, 1, 1, 1, 228, 1, 1, 229, 1, 1, 1, 1, 1, 1, 230, 1, 231, 1, 0 }; local _parse_sql_trans_targs = { [0] = 2, 0, 196, 4, 4, 5, 196, 7, 8, 9, 10, 11, 12, 13, 36, 14, 15, 16, 17, 18, 19, 20, 21, 21, 22, 24, 27, 23, 25, 25, 26, 28, 28, 29, 30, 30, 31, 33, 32, 34, 34, 35, 37, 38, 39, 40, 41, 42, 56, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 57, 57, 57, 57, 58, 59, 60, 61, 62, 92, 63, 64, 71, 82, 89, 65, 66, 67, 68, 69, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 90, 90, 90, 90, 91, 70, 92, 93, 196, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 116, 117, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 133, 134, 137, 134, 135, 136, 138, 139, 140, 141, 142, 143, 144, 145, 150, 151, 154, 146, 146, 147, 157, 146, 146, 147, 157, 148, 149, 196, 144, 151, 148, 149, 152, 153, 155, 156, 147, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 171, 172, 173, 174, 175, 176, 177, 179, 180, 181, 181, 182, 184, 195, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 1, 3, 6, 94, 118, 158, 178, 183 }; local _parse_sql_trans_actions = { [0] = 1, 0, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 30, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 26, 3, 3, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 20, 1, 3, 42, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 5, 5, 1, 5, 20, 46, 5, 1, 3, 34, 1, 14, 1, 17, 1, 1, 51, 38, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; local parse_sql_start = 196; local parse_sql_first_final = 196; local parse_sql_error = 0; local parse_sql_en_main = 196; local _sql_unescapes = setmetatable({ ["\\0"] = "\0"; ["\\'"] = "'"; ["\\\""] = "\""; ["\\b"] = "\b"; ["\\n"] = "\n"; ["\\r"] = "\r"; ["\\t"] = "\t"; ["\\Z"] = "\26"; ["\\\\"] = "\\"; ["\\%"] = "%"; ["\\_"] = "_"; },{ __index = function(t, s) assert(false, "Unknown escape sequences: "..s); end }); function parse_sql(data, h) local p = 1; local pe = #data + 1; local cs; local pos_char, pos_line = 1, 1; local mark, token; local table_name, columns, value_lists, value_list, value_count; cs = parse_sql_start; -- ragel flat exec local testEof = false; local _slen = 0; local _trans = 0; local _keys = 0; local _inds = 0; local _acts = 0; local _nacts = 0; local _tempval = 0; local _goto_level = 0; local _resume = 10; local _eof_trans = 15; local _again = 20; local _test_eof = 30; local _out = 40; while true do -- goto loop local _continue = false; repeat local _trigger_goto = false; if _goto_level <= 0 then -- noEnd if p == pe then _goto_level = _test_eof; _continue = true; break; end -- errState != 0 if cs == 0 then _goto_level = _out; _continue = true; break; end end -- _goto_level <= 0 if _goto_level <= _resume then _keys = cs * 2; -- LOCATE_TRANS _inds = _parse_sql_index_offsets[cs]; _slen = _parse_sql_key_spans[cs]; if _slen > 0 and _parse_sql_trans_keys[_keys] <= data:byte(p) and data:byte(p) <= _parse_sql_trans_keys[_keys + 1] then _trans = _parse_sql_indicies[ _inds + data:byte(p) - _parse_sql_trans_keys[_keys] ]; else _trans =_parse_sql_indicies[ _inds + _slen ]; end cs = _parse_sql_trans_targs[_trans]; if _parse_sql_trans_actions[_trans] ~= 0 then _acts = _parse_sql_trans_actions[_trans]; _nacts = _parse_sql_actions[_acts]; _acts = _acts + 1; while _nacts > 0 do _nacts = _nacts - 1; _acts = _acts + 1; _tempval = _parse_sql_actions[_acts - 1]; -- start action switch if _tempval == 0 then --4 FROM_STATE_ACTION_SWITCH -- line 34 "sql.rl" -- end of line directive pos_char = pos_char + 1; -- ACTION elseif _tempval == 1 then --4 FROM_STATE_ACTION_SWITCH -- line 35 "sql.rl" -- end of line directive pos_line = pos_line + 1; pos_char = 1; -- ACTION elseif _tempval == 2 then --4 FROM_STATE_ACTION_SWITCH -- line 38 "sql.rl" -- end of line directive mark = p; -- ACTION elseif _tempval == 3 then --4 FROM_STATE_ACTION_SWITCH -- line 39 "sql.rl" -- end of line directive token = data:sub(mark, p-1); -- ACTION elseif _tempval == 4 then --4 FROM_STATE_ACTION_SWITCH -- line 52 "sql.rl" -- end of line directive table.insert(columns, token); columns[#columns] = token; -- ACTION elseif _tempval == 5 then --4 FROM_STATE_ACTION_SWITCH -- line 58 "sql.rl" -- end of line directive table_name,columns = token,{}; -- ACTION elseif _tempval == 6 then --4 FROM_STATE_ACTION_SWITCH -- line 59 "sql.rl" -- end of line directive h.create(table_name, columns); -- ACTION elseif _tempval == 7 then --4 FROM_STATE_ACTION_SWITCH -- line 65 "sql.rl" -- end of line directive value_count = value_count + 1; value_list[value_count] = token:gsub("\\.", _sql_unescapes); -- ACTION elseif _tempval == 8 then --4 FROM_STATE_ACTION_SWITCH -- line 68 "sql.rl" -- end of line directive value_count = value_count + 1; value_list[value_count] = tonumber(token); -- ACTION elseif _tempval == 9 then --4 FROM_STATE_ACTION_SWITCH -- line 69 "sql.rl" -- end of line directive value_count = value_count + 1; -- ACTION elseif _tempval == 10 then --4 FROM_STATE_ACTION_SWITCH -- line 71 "sql.rl" -- end of line directive value_list,value_count = {},0; -- ACTION elseif _tempval == 11 then --4 FROM_STATE_ACTION_SWITCH -- line 71 "sql.rl" -- end of line directive table.insert(value_lists, value_list); -- ACTION elseif _tempval == 12 then --4 FROM_STATE_ACTION_SWITCH -- line 74 "sql.rl" -- end of line directive table_name,value_lists = token,{}; -- ACTION elseif _tempval == 13 then --4 FROM_STATE_ACTION_SWITCH -- line 75 "sql.rl" -- end of line directive h.insert(table_name, value_lists); -- ACTION end -- line 355 "sql.lua" -- end of line directive -- end action switch end -- while _nacts end if _trigger_goto then _continue = true; break; end end -- endif if _goto_level <= _again then if cs == 0 then _goto_level = _out; _continue = true; break; end p = p + 1; if p ~= pe then _goto_level = _resume; _continue = true; break; end end -- _goto_level <= _again if _goto_level <= _test_eof then end -- _goto_level <= _test_eof if _goto_level <= _out then break; end _continue = true; until true; if not _continue then break; end end -- endif _goto_level <= out -- end of execute block if cs < parse_sql_first_final then print("parse_sql: there was an error, line "..pos_line.." column "..pos_char); else print("Success. EOF at line "..pos_line.." column "..pos_char) end end end -- import modules package.path = [[C:\Documents and Settings\Waqas\Desktop\mercurial\prosody-hg\?.lua;]]..package.path; -- ugly workaround for getting datamanager to work outside of prosody :( prosody = { }; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "_posix"; end package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager"; dm.set_data_path("data"); local datetime = require "util.datetime"; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; function store_password(username, host, password) -- create or update account for username@host local ret, err = dm.store(username, host, "accounts", {password = password}); print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password); end function store_vcard(username, host, stanza) -- create or update vCard for username@host local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] stored vCard: "..username.."@"..host); end function store_roster(username, host, roster_items) -- fetch current roster-table for username@host if he already has one local roster = dm.load(username, host, "roster") or {}; -- merge imported roster-items with loaded roster for item_tag in roster_items:childtags() do -- jid for this roster-item local item_jid = item_tag.attr.jid -- validate item stanzas if (item_tag.name == "item") and (item_jid ~= "") then -- prepare roster item -- TODO: is the subscription attribute optional? local item = {subscription = item_tag.attr.subscription, groups = {}}; -- optional: give roster item a real name if item_tag.attr.name then item.name = item_tag.attr.name; end -- optional: iterate over group stanzas inside item stanza for group_tag in item_tag:childtags() do local group_name = group_tag:get_text(); if (group_tag.name == "group") and (group_name ~= "") then item.groups[group_name] = true; else print("[error] invalid group stanza: "..group_tag:pretty_print()); end end -- store item in roster roster[item_jid] = item; print("[success] roster entry: " ..username.."@"..host.." - "..item_jid); else print("[error] invalid roster stanza: " ..item_tag:pretty_print()); end end -- store merged roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored roster: " ..username.."@"..host); end function store_subscription_request(username, host, presence_stanza) local from_bare = presence_stanza.attr.from; -- fetch current roster-table for username@host if he already has one local roster = dm.load(username, host, "roster") or {}; local item = roster[from_bare]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- already subscribed, do nothing end -- add to table of pending subscriptions if not roster.pending then roster.pending = {}; end roster.pending[from_bare] = true; -- store updated roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare); end local os_date = os.date; local os_time = os.time; local os_difftime = os.difftime; function datetime_parse(s) if s then local year, month, day, hour, min, sec, tzd; year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$"); if year then local time_offset = os_difftime(os_time(os_date("*t")), os_time(os_date("!*t"))); -- to deal with local timezone local tzd_offset = 0; if tzd ~= "" and tzd ~= "Z" then local sign, h, m = tzd:match("([+%-])(%d%d):?(%d*)"); if not sign then return; end if #m ~= 2 then m = "0"; end h, m = tonumber(h), tonumber(m); tzd_offset = h * 60 * 60 + m * 60; if sign == "-" then tzd_offset = -tzd_offset; end end sec = (sec + time_offset) - tzd_offset; return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false}); end end end function store_offline_messages(username, host, stanza) -- TODO: maybe use list_load(), append and list_store() instead -- of constantly reopening the file with list_append()? --for ch in offline_messages:childtags() do --print("message :"..ch:pretty_print()); stanza.attr.node = nil; local stamp = stanza:get_child("x", "jabber:x:delay"); if not stamp or not stamp.attr.stamp then print(2) return; end for i=1,#stanza do if stanza[i] == stamp then table.remove(stanza, i); break; end end for i=1,#stanza.tags do if stanza.tags[i] == stamp then table.remove(stanza.tags, i); break; end end local parsed_stamp = datetime_parse(stamp.attr.stamp); if not parsed_stamp then print(1, stamp.attr.stamp) return; end stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(parsed_stamp), datetime.legacy(parsed_stamp); local ret, err = dm.list_append(username, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..stanza.attr.from); --end end -- load data local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[XEP-227 importer for Prosody Usage: jabberd14sql2prosody.lua filename.sql ]]); os.exit(1); end local f = io.open(arg); local s = f:read("*a"); f:close(); local table_count = 0; local insert_count = 0; local row_count = 0; -- parse parse_sql(s, { create = function(table_name, columns) --[[print(table_name);]] table_count = table_count + 1; end; insert = function(table_name, value_lists) --[[print(table_name, #value_lists);]] insert_count = insert_count + 1; row_count = row_count + #value_lists; for _,value_list in ipairs(value_lists) do if table_name == "users" then local user, realm, password = unpack(value_list); store_password(user, realm, password); elseif table_name == "roster" then local user, realm, xml = unpack(value_list); local stanza,err = parse_xml(xml); if stanza then store_roster(user, realm, stanza); else print("[error] roster: XML parsing failed for "..user.."@"..realm..": "..err); end elseif table_name == "vcard" then local user, realm, name, email, nickname, birthday, photo, xml = unpack(value_list); if xml then local stanza,err = parse_xml(xml); if stanza then store_vcard(user, realm, stanza); else print("[error] vcard: XML parsing failed for "..user.."@"..realm..": "..err); end else --print("[warn] vcard: NULL vCard for "..user.."@"..realm..": "..err); end elseif table_name == "storedsubscriptionrequests" then local user, realm, fromjid, xml = unpack(value_list); local stanza,err = parse_xml(xml); if stanza then store_subscription_request(user, realm, stanza); else print("[error] storedsubscriptionrequests: XML parsing failed for "..user.."@"..realm..": "..err); end elseif table_name == "messages" then --local user, realm, node, correspondent, type, storetime, delivertime, subject, body, xml = unpack(value_list); local user, realm, type, xml = value_list[1], value_list[2], value_list[5], value_list[10]; if type == "offline" and xml ~= "" then local stanza,err = parse_xml(xml); if stanza then store_offline_messages(user, realm, stanza); else print("[error] offline messages: XML parsing failed for "..user.."@"..realm..": "..err); print(unpack(value_list)); end end end end end; }); print("table_count", table_count); print("insert_count", insert_count); print("row_count", row_count); prosody-0.9.1/tools/xep227toprosody.lua0000755000175000017500000002136112213321667020012 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2009 Matthew Wild -- Copyright (C) 2008-2009 Waqas Hussain -- Copyright (C) 2010 Stefan Gehn -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- FIXME: XEP-0227 supports XInclude but luaexpat does not -- -- XEP-227 elements and their current level of support: -- Hosts : supported -- Users : supported -- Rosters : supported, needs testing -- Offline Messages : supported, needs testing -- Private XML Storage : supported, needs testing -- vCards : supported, needs testing -- Privacy Lists: UNSUPPORTED -- http://xmpp.org/extensions/xep-0227.html#privacy-lists -- mod_privacy uses dm.load(username, host, "privacy"); and stores stanzas 1:1 -- Incoming Subscription Requests : supported package.path = package.path..";../?.lua"; package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager -- ugly workaround for getting datamanager to work outside of prosody :( prosody = { }; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "posix"; end local lxp = require "lxp"; local st = require "util.stanza"; local xmppstream = require "util.xmppstream"; local new_xmpp_handlers = xmppstream.new_sax_handlers; local dm = require "util.datamanager" dm.set_data_path("data"); local ns_separator = xmppstream.ns_separator; local ns_pattern = xmppstream.ns_pattern; local xmlns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns"; ----------------------------------------------------------------------- function store_vcard(username, host, stanza) -- create or update vCard for username@host local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza)); print("["..(err or "success").."] stored vCard: "..username.."@"..host); end function store_password(username, host, password) -- create or update account for username@host local ret, err = dm.store(username, host, "accounts", {password = password}); print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password); end function store_roster(username, host, roster_items) -- fetch current roster-table for username@host if he already has one local roster = dm.load(username, host, "roster") or {}; -- merge imported roster-items with loaded roster for item_tag in roster_items:childtags("item") do -- jid for this roster-item local item_jid = item_tag.attr.jid -- validate item stanzas if (item_jid ~= "") then -- prepare roster item -- TODO: is the subscription attribute optional? local item = {subscription = item_tag.attr.subscription, groups = {}}; -- optional: give roster item a real name if item_tag.attr.name then item.name = item_tag.attr.name; end -- optional: iterate over group stanzas inside item stanza for group_tag in item_tag:childtags("group") do local group_name = group_tag:get_text(); if (group_name ~= "") then item.groups[group_name] = true; else print("[error] invalid group stanza: "..group_tag:pretty_print()); end end -- store item in roster roster[item_jid] = item; print("[success] roster entry: " ..username.."@"..host.." - "..item_jid); else print("[error] invalid roster stanza: " ..item_tag:pretty_print()); end end -- store merged roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored roster: " ..username.."@"..host); end function store_private(username, host, private_items) local private = dm.load(username, host, "private") or {}; for _, ch in ipairs(private_items.tags) do --print("private :"..ch:pretty_print()); private[ch.name..":"..ch.attr.xmlns] = st.preserialize(ch); print("[success] private item: " ..username.."@"..host.." - "..ch.name); end local ret, err = dm.store(username, host, "private", private); print("["..(err or "success").."] stored private: " ..username.."@"..host); end function store_offline_messages(username, host, offline_messages) -- TODO: maybe use list_load(), append and list_store() instead -- of constantly reopening the file with list_append()? for ch in offline_messages:childtags("message", "jabber:client") do --print("message :"..ch:pretty_print()); local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch)); print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from); end end function store_subscription_request(username, host, presence_stanza) local from_bare = presence_stanza.attr.from; -- fetch current roster-table for username@host if he already has one local roster = dm.load(username, host, "roster") or {}; local item = roster[from_bare]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- already subscribed, do nothing end -- add to table of pending subscriptions if not roster.pending then roster.pending = {}; end roster.pending[from_bare] = true; -- store updated roster-table local ret, err = dm.store(username, host, "roster", roster); print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare); end ----------------------------------------------------------------------- local curr_host = ""; local user_name = ""; local cb = { stream_tag = "user", stream_ns = xmlns_xep227, }; function cb.streamopened(session, attr) session.notopen = false; user_name = attr.name; store_password(user_name, curr_host, attr.password); end function cb.streamclosed(session) session.notopen = true; user_name = ""; end function cb.handlestanza(session, stanza) --print("Parsed stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or "")); if (stanza.name == "vCard") and (stanza.attr.xmlns == "vcard-temp") then store_vcard(user_name, curr_host, stanza); elseif (stanza.name == "query") then if (stanza.attr.xmlns == "jabber:iq:roster") then store_roster(user_name, curr_host, stanza); elseif (stanza.attr.xmlns == "jabber:iq:private") then store_private(user_name, curr_host, stanza); end elseif (stanza.name == "offline-messages") then store_offline_messages(user_name, curr_host, stanza); elseif (stanza.name == "presence") and (stanza.attr.xmlns == "jabber:client") then store_subscription_request(user_name, curr_host, stanza); else print("UNHANDLED stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or "")); end end local user_handlers = new_xmpp_handlers({ notopen = true }, cb); ----------------------------------------------------------------------- local lxp_handlers = { --count = 0 }; -- TODO: error handling for invalid opening elements if curr_host is empty function lxp_handlers.StartElement(parser, elementname, attributes) local curr_ns, name = elementname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end --io.write("+ ", string.rep(" ", count), name, " (", curr_ns, ")", "\n") --count = count + 1; if curr_host ~= "" then -- forward to xmlhandlers user_handlers:StartElement(elementname, attributes); elseif (curr_ns == xmlns_xep227) and (name == "host") then curr_host = attributes["jid"]; -- start of host element print("Begin parsing host "..curr_host); elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then io.stderr:write("Unhandled XML element: ", name, "\n"); os.exit(1); end end -- TODO: error handling for invalid closing elements if host is empty function lxp_handlers.EndElement(parser, elementname) local curr_ns, name = elementname:match(ns_pattern); if name == "" then curr_ns, name = "", curr_ns; end --count = count - 1; --io.write("- ", string.rep(" ", count), name, " (", curr_ns, ")", "\n") if curr_host ~= "" then if (curr_ns == xmlns_xep227) and (name == "host") then print("End parsing host "..curr_host); curr_host = "" -- end of host element else -- forward to xmlhandlers user_handlers:EndElement(elementname); end elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then io.stderr:write("Unhandled XML element: ", name, "\n"); os.exit(1); end end function lxp_handlers.CharacterData(parser, string) if curr_host ~= "" then -- forward to xmlhandlers user_handlers:CharacterData(string); end end ----------------------------------------------------------------------- local arg = ...; local help = "/? -? ? /h -h /help -help --help"; if not arg or help:find(arg, 1, true) then print([[XEP-227 importer for Prosody Usage: xep227toprosody.lua filename.xml ]]); os.exit(1); end local file = io.open(arg); if not file then io.stderr:write("Could not open file: ", arg, "\n"); os.exit(0); end local parser = lxp.new(lxp_handlers, ns_separator); for l in file:lines() do parser:parse(l); end parser:parse(); parser:close(); file:close(); prosody-0.9.1/tools/ejabberdsql2prosody.lua0000644000175000017500000002174012213321667020756 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- prosody = {}; package.path = package.path ..";../?.lua"; local serialize = require "util.serialization".serialize; local st = require "util.stanza"; local parse_xml = require "util.xml".parse; package.loaded["util.logger"] = {init = function() return function() end; end} local dm = require "util.datamanager" dm.set_data_path("data"); function parseFile(filename) ------ local file = nil; local last = nil; local line = 1; local function read(expected) local ch; if last then ch = last; last = nil; else ch = file:read(1); if ch == "\n" then line = line + 1; end end if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil").." on line "..line); end return ch; end local function pushback(ch) if last then error(); end last = ch; end local function peek() if not last then last = read(); end return last; end local escapes = { ["\\0"] = "\0"; ["\\'"] = "'"; ["\\\""] = "\""; ["\\b"] = "\b"; ["\\n"] = "\n"; ["\\r"] = "\r"; ["\\t"] = "\t"; ["\\Z"] = "\26"; ["\\\\"] = "\\"; ["\\%"] = "%"; ["\\_"] = "_"; } local function unescape(s) return escapes[s] or error("Unknown escape sequence: "..s); end local function readString() read("'"); local s = ""; while true do local ch = peek(); if ch == "\\" then s = s..unescape(read()..read()); elseif ch == "'" then break; else s = s..read(); end end read("'"); return s; end local function readNonString() local s = ""; while true do if peek() == "," or peek() == ")" then break; else s = s..read(); end end return tonumber(s); end local function readItem() if peek() == "'" then return readString(); else return readNonString(); end end local function readTuple() local items = {} read("("); while peek() ~= ")" do table.insert(items, readItem()); if peek() == ")" then break; end read(","); end read(")"); return items; end local function readTuples() if peek() ~= "(" then read("("); end local tuples = {}; while true do table.insert(tuples, readTuple()); if peek() == "," then read() end if peek() == ";" then break; end end return tuples; end local function readTableName() local tname = ""; while peek() ~= "`" do tname = tname..read(); end return tname; end local function readInsert() if peek() == nil then return nil; end for ch in ("INSERT INTO `"):gmatch(".") do -- find line starting with this if peek() == ch then read(); -- found else -- match failed, skip line while peek() and read() ~= "\n" do end return nil; end end local tname = readTableName(); read("`"); read(" ") -- expect this if peek() == "(" then -- skip column list repeat until read() == ")"; read(" "); end for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this local tuples = readTuples(); read(";"); read("\n"); return tname, tuples; end local function readFile(filename) file = io.open(filename); if not file then error("File not found: "..filename); os.exit(0); end local t = {}; while true do local tname, tuples = readInsert(); if tname then if t[tname] then local t_name = t[tname]; for i=1,#tuples do table.insert(t_name, tuples[i]); end else t[tname] = tuples; end elseif peek() == nil then break; end end return t; end return readFile(filename); ------ end local arg, host = ...; local help = "/? -? ? /h -h /help -help --help"; if not(arg and host) or help:find(arg, 1, true) then print([[ejabberd SQL DB dump importer for Prosody Usage: ejabberdsql2prosody.lua filename.txt hostname The file can be generated using mysqldump: mysqldump db_name > filename.txt]]); os.exit(1); end local map = { ["last"] = {"username", "seconds", "state"}; ["privacy_default_list"] = {"username", "name"}; ["privacy_list"] = {"username", "name", "id"}; ["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"}; ["private_storage"] = {"username", "namespace", "data"}; ["rostergroups"] = {"username", "jid", "grp"}; ["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"}; ["spool"] = {"username", "xml", "seq"}; ["users"] = {"username", "password"}; ["vcard"] = {"username", "vcard"}; --["vcard_search"] = {}; } local NULL = {}; local t = parseFile(arg); for name, data in pairs(t) do local m = map[name]; if m then if #data > 0 and #data[1] ~= #m then print("[warning] expected "..#m.." columns for table `"..name.."`, found "..#data[1]); end for i=1,#data do local row = data[i]; for j=1,#m do row[m[j]] = row[j]; row[j] = nil; end end end end --print(serialize(t)); for i, row in ipairs(t["users"] or NULL) do local node, password = row.username, row.password; local ret, err = dm.store(node, host, "accounts", {password = password}); print("["..(err or "success").."] accounts: "..node.."@"..host); end function roster(node, host, jid, item) local roster = dm.load(node, host, "roster") or {}; roster[jid] = item; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid); end function roster_pending(node, host, jid) local roster = dm.load(node, host, "roster") or {}; roster.pending = roster.pending or {}; roster.pending[jid] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster-pending: " ..node.."@"..host.." - "..jid); end function roster_group(node, host, jid, group) local roster = dm.load(node, host, "roster") or {}; local item = roster[jid]; if not item then print("Warning: No roster item "..jid.." for user "..node..", can't put in group "..group); return; end item.groups[group] = true; local ret, err = dm.store(node, host, "roster", roster); print("["..(err or "success").."] roster-group: " ..node.."@"..host.." - "..jid.." - "..group); end function private_storage(node, host, xmlns, stanza) local private = dm.load(node, host, "private") or {}; private[stanza.name..":"..xmlns] = st.preserialize(stanza); local ret, err = dm.store(node, host, "private", private); print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns); end function offline_msg(node, host, t, stanza) stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t); stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t); local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza)); print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t)); end for i, row in ipairs(t["rosterusers"] or NULL) do local node, contact = row.username, row.jid; local name = row.nick; if name == "" then name = nil; end local subscription = row.subscription; if subscription == "N" then subscription = "none" elseif subscription == "B" then subscription = "both" elseif subscription == "F" then subscription = "from" elseif subscription == "T" then subscription = "to" else error("Unknown subscription type: "..subscription) end; local ask = row.ask; if ask == "N" then ask = nil; elseif ask == "O" then ask = "subscribe"; elseif ask == "I" then roster_pending(node, host, contact); ask = nil; elseif ask == "B" then roster_pending(node, host, contact); ask = "subscribe"; else error("Unknown ask type: "..ask); end local item = {name = name, ask = ask, subscription = subscription, groups = {}}; roster(node, host, contact, item); end for i, row in ipairs(t["rostergroups"] or NULL) do roster_group(row.username, host, row.jid, row.grp); end for i, row in ipairs(t["vcard"] or NULL) do local ret, err = dm.store(row.username, host, "vcard", st.preserialize(parse_xml(row.vcard))); print("["..(err or "success").."] vCard: "..row.username.."@"..host); end for i, row in ipairs(t["private_storage"] or NULL) do private_storage(row.username, host, row.namespace, parse_xml(row.data)); end table.sort(t["spool"] or NULL, function(a,b) return a.seq < b.seq; end); -- sort by sequence number, just in case local time_offset = os.difftime(os.time(os.date("!*t")), os.time(os.date("*t"))) -- to deal with timezones local date_parse = function(s) local year, month, day, hour, min, sec = s:match("(....)-?(..)-?(..)T(..):(..):(..)"); return os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec-time_offset}); end for i, row in ipairs(t["spool"] or NULL) do local stanza = parse_xml(row.xml); local last_child = stanza.tags[#stanza.tags]; if not last_child or last_child ~= stanza[#stanza] then error("Last child of offline message is not a tag"); end if last_child.name ~= "x" and last_child.attr.xmlns ~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end stanza[#stanza], stanza.tags[#stanza.tags] = nil, nil; local t = date_parse(last_child.attr.stamp); offline_msg(row.username, host, t, stanza); end prosody-0.9.1/fallbacks/0000775000175000017500000000000012213321667015053 5ustar matthewmatthewprosody-0.9.1/fallbacks/lxp.lua0000644000175000017500000000706512213321667016367 0ustar matthewmatthew local coroutine = coroutine; local tonumber = tonumber; local string = string; local setmetatable, getmetatable = setmetatable, getmetatable; local pairs = pairs; local deadroutine = coroutine.create(function() end); coroutine.resume(deadroutine); module("lxp") local entity_map = setmetatable({ ["amp"] = "&"; ["gt"] = ">"; ["lt"] = "<"; ["apos"] = "'"; ["quot"] = "\""; }, {__index = function(_, s) if s:sub(1,1) == "#" then if s:sub(2,2) == "x" then return string.char(tonumber(s:sub(3), 16)); else return string.char(tonumber(s:sub(2))); end end end }); local function xml_unescape(str) return (str:gsub("&(.-);", entity_map)); end local function parse_tag(s) local name,sattr=(s):gmatch("([^%s]+)(.*)")(); local attr = {}; for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end return name, attr; end local function parser(data, handlers, ns_separator) local function read_until(str) local pos = data:find(str, nil, true); while not pos do data = data..coroutine.yield(); pos = data:find(str, nil, true); end local r = data:sub(1, pos); data = data:sub(pos+1); return r; end local function read_before(str) local pos = data:find(str, nil, true); while not pos do data = data..coroutine.yield(); pos = data:find(str, nil, true); end local r = data:sub(1, pos-1); data = data:sub(pos); return r; end local function peek() while #data == 0 do data = coroutine.yield(); end return data:sub(1,1); end local ns = { xml = "http://www.w3.org/XML/1998/namespace" }; ns.__index = ns; local function apply_ns(name, dodefault) local prefix,n = name:match("^([^:]*):(.*)$"); if prefix and ns[prefix] then return ns[prefix]..ns_separator..n; end if dodefault and ns[""] then return ns[""]..ns_separator..name; end return name; end local function push(tag, attr) ns = setmetatable({}, ns); for k,v in pairs(attr) do local xmlns = k == "xmlns" and "" or k:match("^xmlns:(.*)$"); if xmlns then ns[xmlns] = v; attr[k] = nil; end end local newattr, n = {}, 0; for k,v in pairs(attr) do n = n+1; k = apply_ns(k); newattr[n] = k; newattr[k] = v; end tag = apply_ns(tag, true); ns[0] = tag; ns.__index = ns; return tag, newattr; end local function pop() local tag = ns[0]; ns = getmetatable(ns); return tag; end while true do if peek() == "<" then local elem = read_until(">"):sub(2,-2); if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions elseif elem:sub(1,1) == "/" then -- end tag elem = elem:sub(2); local name = pop(); handlers:EndElement(name); -- TODO check for start-end tag name match elseif elem:sub(-1,-1) == "/" then -- empty tag elem = elem:sub(1,-2); local name,attr = parse_tag(elem); name,attr = push(name,attr); handlers:StartElement(name,attr); name = pop(); handlers:EndElement(name); else -- start tag local name,attr = parse_tag(elem); name,attr = push(name,attr); handlers:StartElement(name,attr); end else local text = read_before("<"); handlers:CharacterData(xml_unescape(text)); end end end function new(handlers, ns_separator) local co = coroutine.create(parser); return { parse = function(self, data) if not data then co = deadroutine; return true; -- eof end local success, result = coroutine.resume(co, data, handlers, ns_separator); if result then co = deadroutine; return nil, result; -- error end return true; -- success end; }; end return _M; prosody-0.9.1/fallbacks/bit.lua0000644000175000017500000002360312213321667016336 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local type = type; local tonumber = tonumber; local setmetatable = setmetatable; local error = error; local tostring = tostring; local print = print; local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;}; local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;}; local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;} local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;}; local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;}; local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;}; local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;}; local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;}; local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;}; module "bit" local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end}; local function do_bop(a, b, op) return setmetatable({ op[a[1]*16+b[1]]; op[a[2]*16+b[2]]; op[a[3]*16+b[3]]; op[a[4]*16+b[4]]; op[a[5]*16+b[5]]; op[a[6]*16+b[6]]; op[a[7]*16+b[7]]; op[a[8]*16+b[8]]; }, bit_mt); end local function do_uop(a, op) return setmetatable({ op[a[1]]; op[a[2]]; op[a[3]]; op[a[4]]; op[a[5]]; op[a[6]]; op[a[7]]; op[a[8]]; }, bit_mt); end function bxor(a, b) return do_bop(a, b, xor_map); end function bor(a, b) return do_bop(a, b, or_map); end function band(a, b) return do_bop(a, b, and_map); end function bnot(a) return do_uop(a, not_map); end local function _rshift1(t) local carry = 0; for i=1,8 do local t_i = rshift1_map[t[i]] + carry; carry = rshift1carry_map[t[i]]; t[i] = t_i; end end function rshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for n = 1,i do _rshift1(t); end return setmetatable(t, bit_mt); end local function _arshift1(t) local carry = arshift1carry_map[t[1]]; for i=1,8 do local t_i = rshift1_map[t[i]] + carry; carry = rshift1carry_map[t[i]]; t[i] = t_i; end end function arshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for n = 1,i do _arshift1(t); end return setmetatable(t, bit_mt); end local function _lshift1(t) local carry = 0; for i=8,1,-1 do local t_i = lshift1_map[t[i]] + carry; carry = lshift1carry_map[t[i]]; t[i] = t_i; end end function lshift(a, i) local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; for n = 1,i do _lshift1(t); end return setmetatable(t, bit_mt); end local function _cast(a) if type(a) == "number" then a = ("%x"):format(a); elseif type(a) == "table" then return a; elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end local t = {0,0,0,0,0,0,0,0}; a = "00000000"..a; a = a:sub(-8); for i = 1,8 do t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2); end return setmetatable(t, bit_mt); end local function wrap1(f) return function(a, ...) if type(a) ~= "table" then a = _cast(a); end a = f(a, ...); a = tonumber(tostring(a), 16); if a > 0x7fffffff then a = a - 1 - 0xffffffff; end return a; end; end local function wrap2(f) return function(a, b, ...) if type(a) ~= "table" then a = _cast(a); end if type(b) ~= "table" then b = _cast(b); end a = f(a, b, ...); a = tonumber(tostring(a), 16); if a > 0x7fffffff then a = a - 1 - 0xffffffff; end return a; end; end bxor = wrap2(bxor); bor = wrap2(bor); band = wrap2(band); bnot = wrap1(bnot); lshift = wrap1(lshift); rshift = wrap1(rshift); arshift = wrap1(arshift); cast = wrap1(_cast); bits = 32; return _M; prosody-0.9.1/configure0000755000175000017500000002073312213321667015043 0ustar matthewmatthew#!/bin/sh # Defaults PREFIX=/usr/local SYSCONFDIR="$PREFIX/etc/prosody" DATADIR="$PREFIX/var/lib/prosody" LUA_SUFFIX="" LUA_DIR="/usr" LUA_BINDIR="/usr/bin" LUA_INCDIR="/usr/include" LUA_LIBDIR="/usr/lib" IDN_LIB=idn ICU_FLAGS="-licui18n -licudata -licuuc" OPENSSL_LIB=crypto CC=gcc CXX=g++ LD=gcc RUNWITH=lua CFLAGS="-fPIC -Wall" LDFLAGS="-shared" IDN_LIBRARY=idn # Help show_help() { cat < config.unix # This file was automatically generated by the configure script. # Run "./configure --help" for details. PREFIX=$PREFIX SYSCONFDIR=$SYSCONFDIR DATADIR=$DATADIR LUA_SUFFIX=$LUA_SUFFIX LUA_DIR=$LUA_DIR LUA_INCDIR=$LUA_INCDIR LUA_LIBDIR=$LUA_LIBDIR LUA_BINDIR=$LUA_BINDIR REQUIRE_CONFIG=$REQUIRE_CONFIG IDN_LIB=$IDN_LIB IDNA_LIBS=$IDNA_LIBS OPENSSL_LIB=$OPENSSL_LIB CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS CC=$CC CXX=$CXX LD=$LD RUNWITH=$RUNWITH EOF echo "Installation prefix: $PREFIX" echo "Prosody configuration directory: $SYSCONFDIR" echo "Using Lua from: $LUA_DIR" make clean > /dev/null 2> /dev/null echo echo "Done. You can now run 'make' to build." echo prosody-0.9.1/doc/0000775000175000017500000000000012213321667013676 5ustar matthewmatthewprosody-0.9.1/doc/stanza.txt0000644000175000017500000000104612213321667015736 0ustar matthewmatthew Structure of a stanza: stanza { --- properties --- tags -- array of tags --- static methods --- iq(attrs) -- --- read-only methods --- reply -- return new stanza with attributes of current stanza child_with_name(string name) -- return the first child of the current tag with the matching name --- write methods --- tag(name, sttrs) -- create a new child of the current tag, and set the child as current up() -- move to the parent of the current tag text(string) -- append a new text node to the current tag } prosody-0.9.1/doc/names.txt0000644000175000017500000000225212213321667015541 0ustar matthewmatthewlxmppd - ... dia - Greek, 'through', pronounced "dee-ah", root of "dialogue" metaphor - An imaginative comparison between two actions/objects etc which is not literally applicable. minstrel - Itinerant medieval musician/singer/story teller/poet. parody - Imitation of a poem or another poet's style for comic/satiric effect. poesy - Archaic word for poetry. Xinshi - Chinese poetic term which literally means 'new poetry'. polylogue - Many conversations Thorns thought of: poe - Derived from "poetry" poezie - Romanian for "poesy" and "poem" Elain - Just a cool name Elane - A variation Eclaire - Idem (French) Adel - Random Younha - Read as "yuna" Quezacotl - Mayan gods -> google for correct form and pronounciation Carbuncle - FF8 Guardian Force ^^ Protos - Mars satellite mins - Derived from minstrel diapoe - gr. dia + poesy/poetry xinshi - I like it for a name just like that loom - The first application I run on the first day of using a computer Lory - Another name I happen to like Loki - Nordic god of mischief, IIRC Luna - Probably taken but I think worth mentioning Coreo - Random thought Miria - Also random Lora - Idem Kraken - :P Nebula - . prosody-0.9.1/doc/coding_style.txt0000644000175000017500000000163612213321667017126 0ustar matthewmatthewThis file describes some coding styles to try and adhere to when contributing to this project. Please try to follow, and feel free to fix code you see not following this standard. == Indentation == 1 tab indentation for all blocks == Spacing == No space between function names and parenthesis and parenthesis and paramters: function foo(bar, baz) Single space between braces and key/value pairs in table constructors: { foo = "bar", bar = "foo" } == Local variable naming == In this project there are many places where use of globals is restricted, and locals used for faster access. Local versions of standard functions should follow the below form: math.random -> m_random string.char -> s_char == Miscellaneous == Single-statement blocks may be written on one line when short if foo then bar(); end 'do' and 'then' keywords should be placed at the end of the line, and never on a line by themself. prosody-0.9.1/doc/roster_format.txt0000644000175000017500000000113112213321667017317 0ustar matthewmatthew This file documents the structure of the roster object. table roster { [string bare_jid] = roster_item } table roster_item { string subscription = "none" | "to" | "from" | "both" string name = Opaque string set by client. (optional) set groups = a set of opaque strings set by the client boolean ask = nil | "subscribe" - a value of true indicates subscription is pending } The roster is available as hosts[host].sessions[username].roster and a copy is made to session.roster for all sessions. All modifications to a roster should be done through the rostermanager. prosody-0.9.1/doc/stanza_routing.txt0000644000175000017500000000152512213321667017507 0ustar matthewmatthewNo 'to' attribute: IQ: Pass to appropriate handler Presence: Broadcast to contacts - if initial presence, also send out presence probes - if probe would be to local user, generate presence stanza for them Message: Route as if it is addressed to the bare JID of the sender To a local host: IQ: Pass to appropriate handler Presence: - Message: Deliver to admin? To local contact: Bare JID: IQ: Pass to appropriate handler Presence: Broadcast to all resources Message: Route to 'best' resource Full JID: IQ: Send to resource Presence: Send to resource Message: Send to resource Full JID but resource not connected: IQ: Return service-unavailable Message: Handle same as if to bare JID Presence: Drop (unless type=subscribe[ed]) To remote contact: Initiate s2s connection if necessary Send stanza across prosody-0.9.1/doc/session.txt0000644000175000017500000000321112213321667016115 0ustar matthewmatthew Structure of a session: session { -- properties -- conn -- the tcp connection notopen -- true if stream has not been initiated, removed after receiving type -- the connection type. Valid values include: -- "c2s_unauthed" - connection has not been authenticated yet -- "c2s" - from a local client to the server username -- the node part of the client's jid (not defined before auth) host -- the host part of the client's jid (not defined before stream initiation) resource -- the resource part of the client's full jid (not defined before resource binding) full_jid -- convenience for the above 3 as string in username@host/resource form (not defined before resource binding) priority -- the resource priority, default: 0 presence -- the last non-directed presence with no type attribute. initially nil. reset to nil on unavailable presence. interested -- true if the resource requested the roster. Interested resources recieve roster updates. Initially nil. roster -- the user's roster. Loaded as soon as the resource is bound (session becomes a connected resource). -- methods -- send(x) -- converts x to a string, and writes it to the connection disconnect(x) -- Disconnect the user and clean up the session, best call sessionmanager.destroy_session() instead of this in most cases } if session.full_jid (also session.roster and session.resource) then this is a "connected resource" if session.presence then this is an "available resource" (all available resources are connected resources) if session.interested then this is an "interested resource" (all interested resources are connected resources) prosody-0.9.1/net/0000775000175000017500000000000012213321667013717 5ustar matthewmatthewprosody-0.9.1/net/server_select.lua0000644000175000017500000006632412213321667017300 0ustar matthewmatthew-- -- server.lua by blastbeat of the luadch project -- Re-used here under the MIT/X Consortium License -- -- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain -- -- // wrapping luadch stuff // -- local use = function( what ) return _G[ what ] end local log, table_concat = require ("util.logger").init("socket"), table.concat; local out_put = function (...) return log("debug", table_concat{...}); end local out_error = function (...) return log("warn", table_concat{...}); end ----------------------------------// DECLARATION //-- --// constants //-- local STAT_UNIT = 1 -- byte --// lua functions //-- local type = use "type" local pairs = use "pairs" local ipairs = use "ipairs" local tonumber = use "tonumber" local tostring = use "tostring" --// lua libs //-- local os = use "os" local table = use "table" local string = use "string" local coroutine = use "coroutine" --// lua lib methods //-- local os_difftime = os.difftime local math_min = math.min local math_huge = math.huge local table_concat = table.concat local string_sub = string.sub local coroutine_wrap = coroutine.wrap local coroutine_yield = coroutine.yield --// extern libs //-- local luasec = use "ssl" local luasocket = use "socket" or require "socket" local luasocket_gettime = luasocket.gettime --// extern lib methods //-- local ssl_wrap = ( luasec and luasec.wrap ) local socket_bind = luasocket.bind local socket_sleep = luasocket.sleep local socket_select = luasocket.select --// functions //-- local id local loop local stats local idfalse local closeall local addsocket local addserver local addtimer local getserver local wrapserver local getsettings local closesocket local removesocket local removeserver local wrapconnection local changesettings --// tables //-- local _server local _readlist local _timerlist local _sendlist local _socketlist local _closelist local _readtimes local _writetimes --// simple data types //-- local _ local _readlistlen local _sendlistlen local _timerlistlen local _sendtraffic local _readtraffic local _selecttimeout local _sleeptime local _tcpbacklog local _starttime local _currenttime local _maxsendlen local _maxreadlen local _checkinterval local _sendtimeout local _readtimeout local _timer local _maxselectlen local _maxfd local _maxsslhandshake ----------------------------------// DEFINITION //-- _server = { } -- key = port, value = table; list of listening servers _readlist = { } -- array with sockets to read from _sendlist = { } -- arrary with sockets to write to _timerlist = { } -- array of timer functions _socketlist = { } -- key = socket, value = wrapped socket (handlers) _readtimes = { } -- key = handler, value = timestamp of last data reading _writetimes = { } -- key = handler, value = timestamp of last data writing/sending _closelist = { } -- handlers to close _readlistlen = 0 -- length of readlist _sendlistlen = 0 -- length of sendlist _timerlistlen = 0 -- lenght of timerlist _sendtraffic = 0 -- some stats _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 _checkinterval = 1200000 -- interval in secs to check idle clients _sendtimeout = 60000 -- allowed send idle time in secs _readtimeout = 6 * 60 * 60 -- allowed read idle time in secs local is_windows = package.config:sub(1,1) == "\\" -- check the directory separator, to detemine whether this is Windows _maxfd = (is_windows and math.huge) or luasocket._SETSIZE or 1024 -- max fd number, limit to 1024 by default to prevent glibc buffer overflow, but not on Windows _maxselectlen = luasocket._SETSIZE or 1024 -- But this still applies on Windows _maxsslhandshake = 30 -- max handshake round-trips ----------------------------------// PRIVATE //-- wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- this function wraps a server -- FIXME Make sure FD < _maxfd if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) socket:close() return nil, "fd-too-large" end local connections = 0 local dispatch, disconnect = listeners.onconnect, listeners.ondisconnect local accept = socket.accept --// public methods of the object //-- local handler = { } handler.shutdown = function( ) end handler.ssl = function( ) return sslctx ~= nil end handler.sslctx = function( ) return sslctx end handler.remove = function( ) connections = connections - 1 if handler then handler.resume( ) end end handler.close = function() socket:close( ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _readlistlen = removesocket( _readlist, socket, _readlistlen ) _server[ip..":"..serverport] = nil; _socketlist[ socket ] = nil handler = nil socket = nil --mem_free( ) out_put "server.lua: closed server handler and removed sockets from list" end handler.pause = function( hard ) if not handler.paused then _readlistlen = removesocket( _readlist, socket, _readlistlen ) if hard then _socketlist[ socket ] = nil socket:close( ) socket = nil; end handler.paused = true; end end handler.resume = function( ) if handler.paused then if not socket then socket = socket_bind( ip, serverport, _tcpbacklog ); socket:settimeout( 0 ) end _readlistlen = addsocket(_readlist, socket, _readlistlen) _socketlist[ socket ] = handler handler.paused = false; end end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.socket = function( ) return socket end handler.readbuffer = function( ) if _readlistlen >= _maxselectlen or _sendlistlen >= _maxselectlen then handler.pause( ) out_put( "server.lua: refused new client connection: server full" ) return false end local client, err = accept( socket ) -- try to accept if client then local ip, clientport = client:getpeername( ) local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx ) -- wrap new client socket if err then -- error while wrapping ssl socket return false end connections = connections + 1 out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) if dispatch and not sslctx then -- SSL connections will notify onconnect when handshake completes return dispatch( handler ); end return; elseif err then -- maybe timeout or something else out_put( "server.lua: error with new client connection: ", tostring(err) ) return false end end return handler end wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx ) -- this function wraps a client to a handler object if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) -- PROTIP: Switch to libevent socket:close( ) -- Should we send some kind of error here? if server then server.pause( ) end return nil, nil, "fd-too-large" end socket:settimeout( 0 ) --// local import of socket methods //-- local send local receive local shutdown --// private closures of the object //-- local ssl local dispatch = listeners.onincoming local status = listeners.onstatus local disconnect = listeners.ondisconnect local drain = listeners.ondrain local bufferqueue = { } -- buffer array local bufferqueuelen = 0 -- end of buffer array local toclose local fatalerror local needtls local bufferlen = 0 local noread = false local nosend = false local sendtraffic, readtraffic = 0, 0 local maxsendlen = _maxsendlen local maxreadlen = _maxreadlen --// public methods of the object //-- local handler = bufferqueue -- saves a table ^_^ handler.dispatch = function( ) return dispatch end handler.disconnect = function( ) return disconnect end handler.setlistener = function( self, listeners ) dispatch = listeners.onincoming disconnect = listeners.ondisconnect status = listeners.onstatus drain = listeners.ondrain end handler.getstats = function( ) return readtraffic, sendtraffic end handler.ssl = function( ) return ssl end handler.sslctx = function ( ) return sslctx end handler.send = function( _, data, i, j ) return send( socket, data, i, j ) end handler.receive = function( pattern, prefix ) return receive( socket, pattern, prefix ) end handler.shutdown = function( pattern ) return shutdown( socket, pattern ) end handler.setoption = function (self, option, value) if socket.setoption then return socket:setoption(option, value); end return false, "setoption not implemented"; end handler.force_close = function ( self, err ) if bufferqueuelen ~= 0 then out_put("server.lua: discarding unwritten data for ", tostring(ip), ":", tostring(clientport)) bufferqueuelen = 0; end return self:close(err); end handler.close = function( self, err ) if not handler then return true; end _readlistlen = removesocket( _readlist, socket, _readlistlen ) _readtimes[ handler ] = nil if bufferqueuelen ~= 0 then handler.sendbuffer() -- Try now to send any outstanding data if bufferqueuelen ~= 0 then -- Still not empty, so we'll try again later if handler then handler.write = nil -- ... but no further writing allowed end toclose = true return false end end if socket then _ = shutdown and shutdown( socket ) socket:close( ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _socketlist[ socket ] = nil socket = nil else out_put "server.lua: socket already closed" end if handler then _writetimes[ handler ] = nil _closelist[ handler ] = nil local _handler = handler; handler = nil if disconnect then disconnect(_handler, err or false); disconnect = nil end end if server then server.remove( ) end out_put "server.lua: closed client handler and removed socket from list" return true end handler.ip = function( ) return ip end handler.serverport = function( ) return serverport end handler.clientport = function( ) return clientport end local write = function( self, data ) bufferlen = bufferlen + #data if bufferlen > maxsendlen then _closelist[ handler ] = "send buffer exceeded" -- cannot close the client at the moment, have to wait to the end of the cycle handler.write = idfalse -- dont write anymore return false elseif socket and not _sendlist[ socket ] then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end bufferqueuelen = bufferqueuelen + 1 bufferqueue[ bufferqueuelen ] = data if handler then _writetimes[ handler ] = _writetimes[ handler ] or _currenttime end return true end handler.write = write handler.bufferqueue = function( self ) return bufferqueue end handler.socket = function( self ) return socket end handler.set_mode = function( self, new ) pattern = new or pattern return pattern end handler.set_send = function ( self, newsend ) send = newsend or send return send end handler.bufferlen = function( self, readlen, sendlen ) maxsendlen = sendlen or maxsendlen maxreadlen = readlen or maxreadlen return bufferlen, maxreadlen, maxsendlen end --TODO: Deprecate handler.lock_read = function (self, switch) if switch == true then local tmp = _readlistlen _readlistlen = removesocket( _readlist, socket, _readlistlen ) _readtimes[ handler ] = nil if _readlistlen ~= tmp then noread = true end elseif switch == false then if noread then noread = false _readlistlen = addsocket(_readlist, socket, _readlistlen) _readtimes[ handler ] = _currenttime end end return noread end handler.pause = function (self) return self:lock_read(true); end handler.resume = function (self) return self:lock_read(false); end handler.lock = function( self, switch ) handler.lock_read (switch) if switch == true then handler.write = idfalse local tmp = _sendlistlen _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _writetimes[ handler ] = nil if _sendlistlen ~= tmp then nosend = true end elseif switch == false then handler.write = write if nosend then nosend = false write( "" ) end end return noread, nosend end local _readbuffer = function( ) -- this function reads data local buffer, err, part = receive( socket, pattern ) -- receive buffer with "pattern" if not err or (err == "wantread" or err == "timeout") then -- received something local buffer = buffer or part or "" local len = #buffer if len > maxreadlen then handler:close( "receive buffer exceeded" ) return false end local count = len * STAT_UNIT readtraffic = readtraffic + count _readtraffic = _readtraffic + count _readtimes[ handler ] = _currenttime --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err ) return dispatch( handler, buffer, err ) else -- connections was closed or fatal error out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) ) fatalerror = true _ = handler and handler:force_close( err ) return false end end local _sendbuffer = function( ) -- this function sends data local succ, err, byte, buffer, count; if socket then buffer = table_concat( bufferqueue, "", 1, bufferqueuelen ) succ, err, byte = send( socket, buffer, 1, bufferlen ) count = ( succ or byte or 0 ) * STAT_UNIT sendtraffic = sendtraffic + count _sendtraffic = _sendtraffic + count for i = bufferqueuelen,1,-1 do bufferqueue[ i ] = nil end --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) ) else succ, err, count = false, "unexpected close", 0; end if succ then -- sending succesful bufferqueuelen = 0 bufferlen = 0 _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) -- delete socket from writelist _writetimes[ handler ] = nil if drain then drain(handler) end _ = needtls and handler:starttls(nil) _ = toclose and handler:force_close( ) return true elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write buffer = string_sub( buffer, byte + 1, bufferlen ) -- new buffer bufferqueue[ 1 ] = buffer -- insert new buffer in queue bufferqueuelen = 1 bufferlen = bufferlen - byte _writetimes[ handler ] = _currenttime return true else -- connection was closed during sending or fatal error out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) ) fatalerror = true _ = handler and handler:force_close( err ) return false end end -- Set the sslctx local handshake; function handler.set_sslctx(self, new_sslctx) sslctx = new_sslctx; local read, wrote handshake = coroutine_wrap( function( client ) -- create handshake coroutine local err for i = 1, _maxsslhandshake do _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen read, wrote = nil, nil _, err = client:dohandshake( ) if not err then out_put( "server.lua: ssl handshake done" ) 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 if err == "wantwrite" then _sendlistlen = addsocket(_sendlist, client, _sendlistlen) wrote = true elseif err == "wantread" then _readlistlen = addsocket(_readlist, client, _readlistlen) read = true else break; end err = nil; coroutine_yield( ) -- handshake not finished end 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 end ) end if luasec then handler.starttls = function( self, _sslctx) if _sslctx then handler:set_sslctx(_sslctx); end if bufferqueuelen > 0 then out_put "server.lua: we need to do tls, but delaying until send buffer empty" needtls = true return end out_put( "server.lua: attempting to start tls on " .. tostring( socket ) ) local oldsocket, err = socket socket, err = ssl_wrap( socket, sslctx ) -- wrap socket if not socket then out_put( "server.lua: error while starting tls on client: ", tostring(err or "unknown error") ) return nil, err -- fatal error end socket:settimeout( 0 ) -- add the new socket to our system send = socket.send receive = socket.receive shutdown = id _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) -- remove traces of the old socket _readlistlen = removesocket( _readlist, oldsocket, _readlistlen ) _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen ) _socketlist[ oldsocket ] = nil handler.starttls = nil needtls = nil -- Secure now (if handshake fails connection will close) ssl = true handler.readbuffer = handshake handler.sendbuffer = handshake return handshake( socket ) -- do handshake end end handler.readbuffer = _readbuffer handler.sendbuffer = _sendbuffer send = socket.send receive = socket.receive shutdown = ( ssl and id ) or socket.shutdown _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) 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 end return handler, socket end id = function( ) end idfalse = function( ) return false end addsocket = function( list, socket, len ) if not list[ socket ] then len = len + 1 list[ len ] = socket list[ socket ] = len end return len; end removesocket = function( list, socket, len ) -- this function removes sockets from a list ( copied from copas ) local pos = list[ socket ] if pos then list[ socket ] = nil local last = list[ len ] list[ len ] = nil if last ~= socket then list[ last ] = pos list[ pos ] = last end return len - 1 end return len end closesocket = function( socket ) _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _readlistlen = removesocket( _readlist, socket, _readlistlen ) _socketlist[ socket ] = nil socket:close( ) --mem_free( ) end local function link(sender, receiver, buffersize) local sender_locked; local _sendbuffer = receiver.sendbuffer; function receiver.sendbuffer() _sendbuffer(); if sender_locked and receiver.bufferlen() < buffersize then sender:lock_read(false); -- Unlock now sender_locked = nil; end end local _readbuffer = sender.readbuffer; function sender.readbuffer() _readbuffer(); if not sender_locked and receiver.bufferlen() >= buffersize then sender_locked = true; sender:lock_read(true); end end end ----------------------------------// PUBLIC //-- addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server local err if type( listeners ) ~= "table" then err = "invalid listener table" end if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" elseif _server[ addr..":"..port ] then err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist" elseif sslctx and not luasec then err = "luasec not found" end if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end addr = addr or "*" local server, err = socket_bind( addr, port, _tcpbacklog ) if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx ) -- wrap new server socket if not handler then server:close( ) return nil, err end server:settimeout( 0 ) _readlistlen = addsocket(_readlist, server, _readlistlen) _server[ addr..":"..port ] = handler _socketlist[ server ] = handler out_put( "server.lua: new "..(sslctx and "ssl " or "").."server listener on '[", addr, "]:", port, "'" ) return handler end getserver = function ( addr, port ) return _server[ addr..":"..port ]; end removeserver = function( addr, port ) local handler = _server[ addr..":"..port ] if not handler then return nil, "no server found on '[" .. addr .. "]:" .. tostring( port ) .. "'" end handler:close( ) _server[ addr..":"..port ] = nil return true end closeall = function( ) for _, handler in pairs( _socketlist ) do handler:close( ) _socketlist[ _ ] = nil end _readlistlen = 0 _sendlistlen = 0 _timerlistlen = 0 _server = { } _readlist = { } _sendlist = { } _timerlist = { } _socketlist = { } --mem_free( ) end 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; send_timeout = _sendtimeout; read_timeout = _readtimeout; max_connections = _maxselectlen; max_ssl_handshake_roundtrips = _maxsslhandshake; highest_allowed_fd = _maxfd; } end changesettings = function( new ) if type( new ) ~= "table" then return nil, "invalid settings table" end _selecttimeout = tonumber( new.select_timeout ) or _selecttimeout _sleeptime = tonumber( new.select_sleep_time ) or _sleeptime _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 _maxsslhandshake = new.max_ssl_handshake_roundtrips or _maxsslhandshake _maxfd = new.highest_allowed_fd or _maxfd return true end addtimer = function( listener ) if type( listener ) ~= "function" then return nil, "invalid listener function" end _timerlistlen = _timerlistlen + 1 _timerlist[ _timerlistlen ] = listener return true end stats = function( ) return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen end local quitting; local function setquitting(quit) quitting = not not quit; end loop = function(once) -- this is the main loop of the program if quitting then return "quitting"; end if once then quitting = "once"; end local next_timer_time = math_huge; repeat local read, write, err = socket_select( _readlist, _sendlist, math_min(_selecttimeout, next_timer_time) ) for i, socket in ipairs( write ) do -- send data waiting in writequeues local handler = _socketlist[ socket ] if handler then handler.sendbuffer( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen end end for i, socket in ipairs( read ) do -- receive data local handler = _socketlist[ socket ] if handler then handler.readbuffer( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen end end for handler, err in pairs( _closelist ) do handler.disconnect( )( handler, err ) handler:force_close() -- forced disconnect _closelist[ handler ] = nil; end _currenttime = luasocket_gettime( ) -- Check for socket timeouts 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 -- Fire timers if _currenttime - _timer >= math_min(next_timer_time, 1) then next_timer_time = math_huge; for i = 1, _timerlistlen do local t = _timerlist[ i ]( _currenttime ) -- fire timers if t then next_timer_time = math_min(next_timer_time, t); end end _timer = _currenttime else next_timer_time = next_timer_time - (_currenttime - _timer); end -- wait some time (0 by default) socket_sleep( _sleeptime ) until quitting; if once and quitting == "once" then quitting = nil; return; end return "quitting" end local function step() return loop(true); end local function get_backend() return "select"; end --// EXPERIMENTAL //-- local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx ) local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx ) if not handler then return nil, err end _socketlist[ socket ] = handler if not sslctx then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) if listeners.onconnect then -- When socket is writeable, call onconnect local _sendbuffer = handler.sendbuffer; handler.sendbuffer = function () _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ); handler.sendbuffer = _sendbuffer; listeners.onconnect(handler); -- If there was data with the incoming packet, handle it now. if #handler:bufferqueue() > 0 then return _sendbuffer(); end end end end return handler, socket end local addclient = function( address, port, listeners, pattern, sslctx ) local client, err = luasocket.tcp( ) if err then return nil, err end client:settimeout( 0 ) _, err = client:connect( address, port ) if err then -- try again local handler = wrapclient( client, address, port, listeners ) else wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx ) end end --// EXPERIMENTAL //-- ----------------------------------// BEGIN //-- use "setmetatable" ( _socketlist, { __mode = "k" } ) use "setmetatable" ( _readtimes, { __mode = "k" } ) use "setmetatable" ( _writetimes, { __mode = "k" } ) _timer = luasocket_gettime( ) _starttime = luasocket_gettime( ) local function setlogger(new_logger) local old_logger = log; if new_logger then log = new_logger; end return old_logger; end ----------------------------------// PUBLIC INTERFACE //-- return { _addtimer = addtimer, addclient = addclient, wrapclient = wrapclient, loop = loop, link = link, step = step, stats = stats, closeall = closeall, addserver = addserver, getserver = getserver, setlogger = setlogger, getsettings = getsettings, setquitting = setquitting, removeserver = removeserver, get_backend = get_backend, changesettings = changesettings, } prosody-0.9.1/net/server_event.lua0000644000175000017500000007304112213321667017134 0ustar matthewmatthew--[[ server.lua based on lua/libevent by blastbeat notes: -- when using luaevent, never register 2 or more EV_READ at one socket, same for EV_WRITE -- you cant even register a new EV_READ/EV_WRITE callback inside another one -- to do some of the above, use timeout events or something what will called from outside -- dont let garbagecollect eventcallbacks, as long they are running -- when using luasec, there are 4 cases of timeout errors: wantread or wantwrite during reading or writing --]] local SCRIPT_NAME = "server_event.lua" local SCRIPT_VERSION = "0.05" local SCRIPT_AUTHOR = "blastbeat" local LAST_MODIFIED = "2009/11/20" local cfg = { MAX_CONNECTIONS = 100000, -- max per server connections (use "ulimit -n" on *nix) MAX_HANDSHAKE_ATTEMPTS= 1000, -- attempts to finish ssl handshake 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 CONNECT_TIMEOUT = 20, -- timeout in seconds for connection attempts CLEAR_DELAY = 5, -- seconds to wait for clearing interface list (and calling ondisconnect listeners) DEBUG = true, -- show debug messages } local function use(x) return rawget(_G, x); end local ipairs = use "ipairs" local string = use "string" local select = use "select" local require = use "require" local tostring = use "tostring" local coroutine = use "coroutine" local setmetatable = use "setmetatable" local t_insert = table.insert local t_concat = table.concat local ssl = use "ssl" local socket = use "socket" or require "socket" local log = require ("util.logger").init("socket") local function debug(...) return log("debug", ("%s "):rep(select('#', ...)), ...) end local vdebug = debug; local bitor = ( function( ) -- thx Rici Lake local hasbit = function( x, p ) return x % ( p + p ) >= p end return function( x, y ) local p = 1 local z = 0 local limit = x > y and x or y while p <= limit do if hasbit( x, p ) or hasbit( y, p ) then z = z + p end p = p + p end return z end end )( ) local event = require "luaevent.core" local base = event.new( ) local EV_READ = event.EV_READ local EV_WRITE = event.EV_WRITE local EV_TIMEOUT = event.EV_TIMEOUT local EV_SIGNAL = event.EV_SIGNAL local EV_READWRITE = bitor( EV_READ, EV_WRITE ) local interfacelist = ( function( ) -- holds the interfaces for sockets local array = { } local len = 0 return function( method, arg ) if "add" == method then len = len + 1 array[ len ] = arg arg:_position( len ) return len elseif "delete" == method then if len <= 0 then return nil, "array is already empty" end local position = arg:_position() -- get position in array if position ~= len then local interface = array[ len ] -- get last interface array[ position ] = interface -- copy it into free position array[ len ] = nil -- free last position interface:_position( position ) -- set new position in array else -- free last position array[ len ] = nil end len = len - 1 return len else return array end end end )( ) -- Client interface methods local interface_mt do interface_mt = {}; interface_mt.__index = interface_mt; local addevent = base.addevent local coroutine_wrap, coroutine_yield = coroutine.wrap,coroutine.yield -- Private methods function interface_mt:_position(new_position) self.position = new_position or self.position return self.position; end function interface_mt:_close() return self:_destroy(); end function interface_mt:_start_connection(plainssl) -- should be called from addclient local callback = function( event ) if EV_TIMEOUT == event then -- timeout during connection self.fatalerror = "connection timeout" self:ontimeout() -- call timeout listener self:_close() debug( "new connection failed. id:", self.id, "error:", self.fatalerror ) else if plainssl and ssl then -- start ssl session self:starttls(self._sslctx, true) else -- normal connection self:_start_session(true) end debug( "new connection established. id:", self.id ) end self.eventconnect = nil return -1 end self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT ) return true end function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl if self.type == "client" then local callback = function( ) self:_lock( false, false, false ) --vdebug( "start listening on client socket with id:", self.id ) self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback if call_onconnect then self:onconnect() end self.eventsession = nil return -1 end self.eventsession = addevent( base, nil, EV_TIMEOUT, callback, 0 ) else self:_lock( false ) --vdebug( "start listening on server socket with id:", self.id ) self.eventread = addevent( base, self.conn, EV_READ, self.readcallback ) -- register callback end return true end function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first --vdebug( "starting ssl session with client id:", self.id ) local _ _ = self.eventread and self.eventread:close( ) -- close events; this must be called outside of the event callbacks! _ = self.eventwrite and self.eventwrite:close( ) self.eventread, self.eventwrite = nil, nil local err self.conn, err = ssl.wrap( self.conn, self._sslctx ) if err then self.fatalerror = err self.conn = nil -- cannot be used anymore if call_onconnect then self.ondisconnect = nil -- dont call this when client isnt really connected end self:_close() debug( "fatal error while ssl wrapping:", err ) return false end self.conn:settimeout( 0 ) -- set non blocking local handshakecallback = coroutine_wrap( function( event ) local _, err local attempt = 0 local maxattempt = cfg.MAX_HANDSHAKE_ATTEMPTS while attempt < maxattempt do -- no endless loop attempt = attempt + 1 debug( "ssl handshake of client with id:"..tostring(self)..", attempt:"..attempt ) if attempt > maxattempt then self.fatalerror = "max handshake attempts exceeded" elseif EV_TIMEOUT == event then self.fatalerror = "timeout during handshake" else _, err = self.conn:dohandshake( ) if not err then self:_lock( false, false, false ) -- unlock the interface; sending, closing etc allowed self.send = self.conn.send -- caching table lookups with new client object self.receive = self.conn.receive if not call_onconnect then -- trigger listener self:onstatus("ssl-handshake-complete"); end self:_start_session( call_onconnect ) debug( "ssl handshake done" ) self.eventhandshake = nil return -1 end if err == "wantwrite" then event = EV_WRITE elseif err == "wantread" then event = EV_READ else debug( "ssl handshake error:", err ) self.fatalerror = err end end if self.fatalerror then if call_onconnect then self.ondisconnect = nil -- dont call this when client isnt really connected end self:_close() debug( "handshake failed because:", self.fatalerror ) self.eventhandshake = nil return -1 end event = coroutine_yield( event, cfg.HANDSHAKE_TIMEOUT ) -- yield this monster... end end ) debug "starting handshake..." self:_lock( false, true, true ) -- unlock read/write events, but keep interface locked self.eventhandshake = addevent( base, self.conn, EV_READWRITE, handshakecallback, cfg.HANDSHAKE_TIMEOUT ) return true end function interface_mt:_destroy() -- close this interface + events and call last listener debug( "closing client with id:", self.id, self.fatalerror ) self:_lock( true, true, true ) -- first of all, lock the interface to avoid further actions local _ _ = self.eventread and self.eventread:close( ) if self.type == "client" then _ = self.eventwrite and self.eventwrite:close( ) _ = self.eventhandshake and self.eventhandshake:close( ) _ = self.eventstarthandshake and self.eventstarthandshake:close( ) _ = self.eventconnect and self.eventconnect:close( ) _ = self.eventsession and self.eventsession:close( ) _ = self.eventwritetimeout and self.eventwritetimeout:close( ) _ = self.eventreadtimeout and self.eventreadtimeout:close( ) _ = self.ondisconnect and self:ondisconnect( self.fatalerror ~= "client to close" and self.fatalerror) -- call ondisconnect listener (wont be the case if handshake failed on connect) _ = self.conn and self.conn:close( ) -- close connection _ = self._server and self._server:counter(-1); self.eventread, self.eventwrite = nil, nil self.eventstarthandshake, self.eventhandshake, self.eventclose = nil, nil, nil self.readcallback, self.writecallback = nil, nil else self.conn:close( ) self.eventread, self.eventclose = nil, nil self.interface, self.readcallback = nil, nil end interfacelist( "delete", self ) return true end function interface_mt:_lock(nointerface, noreading, nowriting) -- lock or unlock this interface or events self.nointerface, self.noreading, self.nowriting = nointerface, noreading, nowriting return nointerface, noreading, nowriting end --TODO: Deprecate function interface_mt:lock_read(switch) if switch then return self:pause(); else return self:resume(); end end function interface_mt:pause() return self:_lock(self.nointerface, true, self.nowriting); end function interface_mt:resume() self:_lock(self.nointerface, false, self.nowriting); if not self.eventread then self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback end end function interface_mt:counter(c) if c then self._connections = self._connections + c end return self._connections end -- Public methods function interface_mt:write(data) if self.nowriting then return nil, "locked" end --vdebug( "try to send data to client, id/data:", self.id, data ) data = tostring( data ) local len = #data local total = len + self.writebufferlen if total > cfg.MAX_SEND_LENGTH then -- check buffer length local err = "send buffer exceeded" debug( "error:", err ) -- to much, check your app return nil, err end t_insert(self.writebuffer, data) -- new buffer self.writebufferlen = total if not self.eventwrite then -- register new write event --vdebug( "register new write event" ) self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT ) end return true end function interface_mt:close() if self.nointerface then return nil, "locked"; end debug( "try to close client connection with id:", self.id ) if self.type == "client" then self.fatalerror = "client to close" if self.eventwrite then -- wait for incomplete write request self:_lock( true, true, false ) debug "closing delayed until writebuffer is empty" return nil, "writebuffer not empty, waiting" else -- close now self:_lock( true, true, true ) self:_close() return true end else debug( "try to close server with id:", tostring(self.id)) self.fatalerror = "server to close" self:_lock( true ) self:_close( 0 ) return true end end function interface_mt:socket() return self.conn end function interface_mt:server() return self._server or self; end function interface_mt:port() return self._port end function interface_mt:serverport() return self._serverport end function interface_mt:ip() return self._ip end function interface_mt:ssl() return self._usingssl end function interface_mt:type() return self._type or "client" end function interface_mt:connections() return self._connections end function interface_mt:address() return self.addr end function interface_mt:set_sslctx(sslctx) self._sslctx = sslctx; if sslctx then self.starttls = nil; -- use starttls() of interface_mt else self.starttls = false; -- prevent starttls() end end function interface_mt:set_mode(pattern) if pattern then self._pattern = pattern; end return self._pattern; end function interface_mt:set_send(new_send) -- No-op, we always use the underlying connection's send end function interface_mt:starttls(sslctx, call_onconnect) debug( "try to start ssl at client id:", self.id ) local err self._sslctx = sslctx; if self._usingssl then -- startssl was already called err = "ssl already active" end if err then debug( "error:", err ) return nil, err end self._usingssl = true self.startsslcallback = function( ) -- we have to start the handshake outside of a read/write event self.startsslcallback = nil self:_start_ssl(call_onconnect); self.eventstarthandshake = nil return -1 end if not self.eventwrite then self:_lock( true, true, true ) -- lock the interface, to not disturb the handshake self.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, self.startsslcallback, 0 ) -- add event to start handshake else -- wait until writebuffer is empty self:_lock( true, true, false ) debug "ssl session delayed until writebuffer is empty..." end self.starttls = false; return true end function interface_mt:setoption(option, value) if self.conn.setoption then return self.conn:setoption(option, value); end return false, "setoption not implemented"; end function interface_mt:setlistener(listener) self.onconnect, self.ondisconnect, self.onincoming, self.ontimeout, self.onstatus = listener.onconnect, listener.ondisconnect, listener.onincoming, listener.ontimeout, listener.onstatus; end -- Stub handlers function interface_mt:onconnect() end function interface_mt:onincoming() end function interface_mt:ondisconnect() end function interface_mt:ontimeout() end function interface_mt:ondrain() end function interface_mt:onstatus() end end -- End of client interface methods local handleclient; do local string_sub = string.sub -- caching table lookups local addevent = base.addevent local socket_gettime = socket.gettime function handleclient( client, ip, port, server, pattern, listener, sslctx ) -- creates an client interface --vdebug("creating client interfacce...") local interface = { type = "client"; conn = client; currenttime = socket_gettime( ); -- safe the origin writebuffer = {}; -- writebuffer writebufferlen = 0; -- length of writebuffer send = client.send; -- caching table lookups receive = client.receive; onconnect = listener.onconnect; -- will be called when client disconnects ondisconnect = listener.ondisconnect; -- will be called when client disconnects onincoming = listener.onincoming; -- will be called when client sends data ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs ondrain = listener.ondrain; -- called when writebuffer is empty onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS) eventread = false, eventwrite = false, eventclose = false, eventhandshake = false, eventstarthandshake = false; -- event handler eventconnect = false, eventsession = false; -- more event handler... eventwritetimeout = false; -- even more event handler... eventreadtimeout = false; fatalerror = false; -- error message writecallback = false; -- will be called on write events readcallback = false; -- will be called on read events nointerface = true; -- lock/unlock parameter of this interface noreading = false, nowriting = false; -- locks of the read/writecallback startsslcallback = false; -- starting handshake callback position = false; -- position of client in interfacelist -- Properties _ip = ip, _port = port, _server = server, _pattern = pattern, _serverport = (server and server:port() or nil), _sslctx = sslctx; -- parameters _usingssl = false; -- client is using ssl; } if not ssl then interface.starttls = false; end interface.id = tostring(interface):match("%x+$"); interface.writecallback = function( event ) -- called on write events --vdebug( "new client write event, id/ip/port:", interface, ip, port ) if interface.nowriting or ( interface.fatalerror and ( "client to close" ~= interface.fatalerror ) ) then -- leave this event --vdebug( "leaving this event because:", interface.nowriting or interface.fatalerror ) interface.eventwrite = false return -1 end if EV_TIMEOUT == event then -- took too long to write some data to socket -> disconnect interface.fatalerror = "timeout during writing" debug( "writing failed:", interface.fatalerror ) interface:_close() interface.eventwrite = false return -1 else -- can write :) if interface._usingssl then -- handle luasec if interface.eventreadtimeout then -- we have to read first local ret = interface.readcallback( ) -- call readcallback --vdebug( "tried to read in writecallback, result:", ret ) end if interface.eventwritetimeout then -- luasec only interface.eventwritetimeout:close( ) -- first we have to close timeout event which where regged after a wantread error interface.eventwritetimeout = false end end interface.writebuffer = { t_concat(interface.writebuffer) } local succ, err, byte = interface.conn:send( interface.writebuffer[1], 1, interface.writebufferlen ) --vdebug( "write data:", interface.writebuffer, "error:", err, "part:", byte ) if succ then -- writing succesful interface.writebuffer[1] = nil interface.writebufferlen = 0 interface:ondrain(); if interface.fatalerror then debug "closing client after writing" interface:_close() -- close interface if needed elseif interface.startsslcallback then -- start ssl connection if needed debug "starting ssl handshake after writing" interface.eventstarthandshake = addevent( base, nil, EV_TIMEOUT, interface.startsslcallback, 0 ) elseif interface.eventreadtimeout then return EV_WRITE, EV_TIMEOUT end interface.eventwrite = nil return -1 elseif byte and (err == "timeout" or err == "wantwrite") then -- want write again --vdebug( "writebuffer is not empty:", err ) interface.writebuffer[1] = string_sub( interface.writebuffer[1], byte + 1, interface.writebufferlen ) -- new buffer interface.writebufferlen = interface.writebufferlen - byte if "wantread" == err then -- happens only with luasec local callback = function( ) interface:_close() interface.eventwritetimeout = nil return -1; end interface.eventwritetimeout = addevent( base, nil, EV_TIMEOUT, callback, cfg.WRITE_TIMEOUT ) -- reg a new timeout event debug( "wantread during write attempt, reg it in readcallback but dont know what really happens next..." ) -- hopefully this works with luasec; its simply not possible to use 2 different write events on a socket in luaevent return -1 end return EV_WRITE, cfg.WRITE_TIMEOUT else -- connection was closed during writing or fatal error interface.fatalerror = err or "fatal error" debug( "connection failed in write event:", interface.fatalerror ) interface:_close() interface.eventwrite = nil return -1 end end end interface.readcallback = function( event ) -- called on read events --vdebug( "new client read event, id/ip/port:", tostring(interface.id), tostring(ip), tostring(port) ) if interface.noreading or interface.fatalerror then -- leave this event --vdebug( "leaving this event because:", tostring(interface.noreading or interface.fatalerror) ) interface.eventread = nil return -1 end if EV_TIMEOUT == event then -- took too long to get some data from client -> disconnect interface.fatalerror = "timeout during receiving" debug( "connection failed:", interface.fatalerror ) interface:_close() interface.eventread = nil return -1 else -- can read if interface._usingssl then -- handle luasec if interface.eventwritetimeout then -- ok, in the past writecallback was regged local ret = interface.writecallback( ) -- call it --vdebug( "tried to write in readcallback, result:", tostring(ret) ) end if interface.eventreadtimeout then interface.eventreadtimeout:close( ) interface.eventreadtimeout = nil end end local buffer, err, part = interface.conn:receive( interface._pattern ) -- receive buffer with "pattern" --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) ) buffer = buffer or part if buffer and #buffer > cfg.MAX_READ_LENGTH then -- check buffer length interface.fatalerror = "receive buffer exceeded" debug( "fatal error:", interface.fatalerror ) interface:_close() interface.eventread = nil return -1 end if err and ( err ~= "timeout" and err ~= "wantread" ) then if "wantwrite" == err then -- need to read on write event if not interface.eventwrite then -- register new write event if needed interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT ) end interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT, function( ) interface:_close() end, cfg.READ_TIMEOUT ) debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." ) -- to be honest i dont know what happens next, if it is allowed to first read, the write etc... else -- connection was closed or fatal error interface.fatalerror = err debug( "connection failed in read event:", interface.fatalerror ) interface:_close() interface.eventread = nil return -1 end else interface.onincoming( interface, buffer, err ) -- send new data to listener end if interface.noreading then interface.eventread = nil; return -1; end return EV_READ, cfg.READ_TIMEOUT end end client:settimeout( 0 ) -- set non blocking setmetatable(interface, interface_mt) interfacelist( "add", interface ) -- add to interfacelist return interface end end local handleserver do function handleserver( server, addr, port, pattern, listener, sslctx ) -- creates an server interface debug "creating server interface..." local interface = { _connections = 0; conn = server; onconnect = listener.onconnect; -- will be called when new client connected eventread = false; -- read event handler eventclose = false; -- close event handler readcallback = false; -- read event callback fatalerror = false; -- error message nointerface = true; -- lock/unlock parameter _ip = addr, _port = port, _pattern = pattern, _sslctx = sslctx; } interface.id = tostring(interface):match("%x+$"); interface.readcallback = function( event ) -- server handler, called on incoming connections --vdebug( "server can accept, id/addr/port:", interface, addr, port ) if interface.fatalerror then --vdebug( "leaving this event because:", self.fatalerror ) interface.eventread = nil return -1 end local delay = cfg.ACCEPT_DELAY if EV_TIMEOUT == event then if interface._connections >= cfg.MAX_CONNECTIONS then -- check connection count debug( "to many connections, seconds to wait for next accept:", delay ) return EV_TIMEOUT, delay -- timeout... else return EV_READ -- accept again end end --vdebug("max connection check ok, accepting...") local client, err = server:accept() -- try to accept; TODO: check err while client do if interface._connections >= cfg.MAX_CONNECTIONS then client:close( ) -- refuse connection debug( "maximal connections reached, refuse client connection; accept delay:", delay ) return EV_TIMEOUT, delay -- delay for next accept attempt end local client_ip, client_port = client:getpeername( ) interface._connections = interface._connections + 1 -- increase connection count local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx ) --vdebug( "client id:", clientinterface, "startssl:", startssl ) if ssl and sslctx then clientinterface:starttls(sslctx, true) else clientinterface:_start_session( true ) end debug( "accepted incoming client connection from:", client_ip or "", client_port or "", "to", port or ""); client, err = server:accept() -- try to accept again end return EV_READ end server:settimeout( 0 ) setmetatable(interface, interface_mt) interfacelist( "add", interface ) interface:_start_session() return interface end end local addserver = ( function( ) return function( addr, port, listener, pattern, sslcfg, startssl ) -- TODO: check arguments --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil") local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE ) -- create server socket if not server then debug( "creating server socket on "..addr.." port "..port.." failed:", err ) return nil, err end local sslctx if sslcfg then if not ssl then debug "fatal error: luasec not found" return nil, "luasec not found" end sslctx, err = sslcfg if err then debug( "error while creating new ssl context for server socket:", err ) return nil, err end end local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler debug( "new server created with id:", tostring(interface)) return interface end end )( ) local addclient, wrapclient do function wrapclient( client, ip, port, listeners, pattern, sslctx ) local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx ) interface:_start_connection(sslctx) return interface, client --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl ) local client, err = socket.tcp() -- creating new socket if not client then debug( "cannot create socket:", err ) return nil, err end client:settimeout( 0 ) -- set nonblocking if localaddr then local res, err = client:bind( localaddr, localport, -1 ) if not res then debug( "cannot bind client:", err ) return nil, err end end local sslctx if sslcfg then -- handle ssl/new context if not ssl then debug "need luasec, but not available" return nil, "luasec not found" end sslctx, err = sslcfg if err then debug( "cannot create new ssl context:", err ) return nil, err end end local res, err = client:connect( addr, serverport ) -- connect if res or ( err == "timeout" ) then local ip, port = client:getsockname( ) local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, startssl ) interface:_start_connection( startssl ) debug( "new connection id:", interface.id ) return interface, err else debug( "new connection failed:", err ) return nil, err end end end local loop = function( ) -- starts the event loop base:loop( ) return "quitting"; end local newevent = ( function( ) local add = base.addevent return function( ... ) return add( base, ... ) end end )( ) local closeallservers = function( arg ) for _, item in ipairs( interfacelist( ) ) do if item.type == "server" then item:close( arg ) end end end local function setquitting(yes) if yes then -- Quit now closeallservers(); base:loopexit(); end end local function get_backend() return base:method(); end -- We need to hold onto the events to stop them -- being garbage-collected local signal_events = {}; -- [signal_num] -> event object local function hook_signal(signal_num, handler) local function _handler(event) local ret = handler(); if ret ~= false then -- Continue handling this signal? return EV_SIGNAL; -- Yes end return -1; -- Close this event end signal_events[signal_num] = base:addevent(signal_num, EV_SIGNAL, _handler); return signal_events[signal_num]; end local function link(sender, receiver, buffersize) local sender_locked; function receiver:ondrain() if sender_locked then sender:resume(); sender_locked = nil; end end function sender:onincoming(data) receiver:write(data); if receiver.writebufferlen >= buffersize then sender_locked = true; sender:pause(); end end end return { cfg = cfg, base = base, loop = loop, link = link, event = event, event_base = base, addevent = newevent, addserver = addserver, addclient = addclient, wrapclient = wrapclient, setquitting = setquitting, closeall = closeallservers, get_backend = get_backend, hook_signal = hook_signal, __NAME = SCRIPT_NAME, __DATE = LAST_MODIFIED, __AUTHOR = SCRIPT_AUTHOR, __VERSION = SCRIPT_VERSION, } prosody-0.9.1/net/server.lua0000644000175000017500000000470612213321667015735 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local use_luaevent = prosody and require "core.configmanager".get("*", "use_libevent"); if use_luaevent then use_luaevent = pcall(require, "luaevent.core"); if not use_luaevent then log("error", "libevent not found, falling back to select()"); end end local server; if use_luaevent then server = require "net.server_event"; -- Overwrite signal.signal() because we need to ask libevent to -- handle them instead local ok, signal = pcall(require, "util.signal"); if ok and signal then local _signal_signal = signal.signal; function signal.signal(signal_id, handler) if type(signal_id) == "string" then signal_id = signal[signal_id:upper()]; end if type(signal_id) ~= "number" then return false, "invalid-signal"; end return server.hook_signal(signal_id, handler); end end else use_luaevent = false; server = require "net.server_select"; end if prosody then local config_get = require "core.configmanager".get; local defaults = {}; for k,v in pairs(server.cfg or server.getsettings()) do defaults[k] = v; end local function load_config() local settings = config_get("*", "network_settings") or {}; 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; HANDSHAKE_TIMEOUT = settings.ssl_handshake_timeout; MAX_CONNECTIONS = settings.max_connections; MAX_HANDSHAKE_ATTEMPTS = settings.max_ssl_handshake_roundtrips; MAX_READ_LENGTH = settings.max_receive_buffer_size; MAX_SEND_LENGTH = settings.max_send_buffer_size; READ_TIMEOUT = settings.read_timeout; WRITE_TIMEOUT = settings.send_timeout; }; for k,default in pairs(defaults) do server.cfg[k] = event_settings[k] or default; end else local select_settings = {}; for k,default in pairs(defaults) do select_settings[k] = settings[k] or default; end server.changesettings(select_settings); end end load_config(); prosody.events.add_handler("config-reloaded", load_config); end -- require "net.server" shall now forever return this, -- ie. server_select or server_event as chosen above. return server; prosody-0.9.1/net/http/0000775000175000017500000000000012213321667014676 5ustar matthewmatthewprosody-0.9.1/net/http/server.lua0000644000175000017500000002202212213321667016703 0ustar matthewmatthew local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local parser_new = require "net.http.parser".new; local events = require "util.events".new(); local addserver = require "net.server".addserver; local log = require "util.logger".init("http.server"); local os_date = os.date; local pairs = pairs; local s_upper = string.upper; local setmetatable = setmetatable; local xpcall = xpcall; local traceback = debug.traceback; local tostring = tostring; local codes = require "net.http.codes"; local _M = {}; local sessions = {}; local listener = {}; local hosts = {}; local default_host; local function is_wildcard_event(event) return event:sub(-2, -1) == "/*"; end 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, { -- 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 -- and then put the array into handlers[curr_event] (and return it) local matching_handlers_set = {}; local handlers_array = {}; for event, handlers_set in pairs(event_map) do if event == curr_event or is_wildcard_event(event) and is_wildcard_match(event, curr_event) then for handler, priority in pairs(handlers_set) do matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority }; table.insert(handlers_array, handler); end end end if #handlers_array > 0 then table.sort(handlers_array, function(b, a) local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b]; for i = 1, #a_score do if a_score[i] ~= b_score[i] then -- If equal, compare next score value return a_score[i] < b_score[i]; end end return false; end); else 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) if handlers_array == nil and is_wildcard_event(curr_event) then -- Invalidate the indexes of all matching events for event in pairs(handlers) do if is_wildcard_match(curr_event, event) then handlers[event] = nil; end end end rawset(handlers, curr_event, handlers_array); end; }); local handle_request; 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[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); function listener.onconnect(conn) local secure = conn:ssl() and true or nil; local pending = {}; local waiting = false; local function process_next() if waiting then log("debug", "can't process_next, waiting"); return; end waiting = true; while sessions[conn] and #pending > 0 do local request = t_remove(pending); --log("debug", "process_next: %s", request.path); --handle_request(conn, request, process_next); _1, _2, _3 = conn, request, process_next; if not xpcall(_handle_request, _traceback_handler) then conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err })); conn:close(); end end --log("debug", "ready for more"); waiting = false; end local function success_cb(request) --log("debug", "success_cb: %s", request.path); if waiting then log("error", "http connection handler is not reentrant: %s", request.path); assert(false, "http connection handler is not reentrant"); end request.secure = secure; t_insert(pending, request); process_next(); end local function error_cb(err) log("debug", "error_cb: %s", err or ""); -- FIXME don't close immediately, wait until we process current stuff -- FIXME if err, send off a bad-request response sessions[conn] = nil; conn:close(); end sessions[conn] = parser_new(success_cb, error_cb); end function listener.ondisconnect(conn) local open_response = conn._http_open_response; if open_response and open_response.on_destroy then open_response.finished = true; open_response:on_destroy(); end sessions[conn] = nil; end function listener.onincoming(conn, data) sessions[conn]:feed(data); end local headerfix = setmetatable({}, { __index = function(t, k) local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": "; t[k] = v; return v; end }); function _M.hijack_response(response, listener) error("TODO"); end function handle_request(conn, request, finish_cb) --log("debug", "handler: %s", request.path); local headers = {}; for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end request.headers = headers; request.conn = conn; local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use 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) or (httpversion == "1.1" and not conn_header:find(",close,", 1, true)); local response_conn_header; if persistent then response_conn_header = "Keep-Alive"; else response_conn_header = httpversion == "1.1" and "close" or nil end local response = { request = request; status_code = 200; headers = { date = date_header, connection = response_conn_header }; persistent = persistent; conn = conn; send = _M.send_response; finish_cb = finish_cb; }; conn._http_open_response = response; local host = (request.headers.host or ""):match("[^:]+"); -- Some sanity checking local err_code, err; if not request.path then err_code, err = 400, "Invalid path"; elseif not hosts[host] then if hosts[default_host] then host = default_host; elseif host then err_code, err = 404, "Unknown host: "..host; else err_code, err = 400, "Missing or invalid 'Host' header"; end end if err then response.status_code = err_code; response:send(events.fire_event("http-error", { code = err_code, message = err })); return; end local event = request.method.." "..host..request.path:match("[^?]*"); local payload = { request = request, response = response }; --log("debug", "Firing event: %s", event); local result = events.fire_event(event, payload); if result ~= nil then if result ~= true then local body; local result_type = type(result); if result_type == "number" then response.status_code = result; if result >= 400 then body = events.fire_event("http-error", { code = result }); end elseif result_type == "string" then body = result; elseif result_type == "table" then for k, v in pairs(result) do if k ~= "headers" then response[k] = v; else for header_name, header_value in pairs(v) do response.headers[header_name] = header_value; end end end end response:send(body); end return; end -- if handler not called, return 404 response.status_code = 404; response:send(events.fire_event("http-error", { code = 404 })); end function _M.send_response(response, body) if response.finished then return; end response.finished = true; response.conn._http_open_response = nil; local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); local headers = response.headers; body = body or response.body or ""; headers.content_length = #body; local output = { status_line }; for k,v in pairs(headers) do t_insert(output, headerfix[k]..v); end t_insert(output, "\r\n\r\n"); t_insert(output, body); response.conn:write(t_concat(output)); if response.on_destroy then response:on_destroy(); response.on_destroy = nil; end if response.persistent then response:finish_cb(); else response.conn:close(); end end function _M.add_handler(event, handler, priority) events.add_handler(event, handler, priority); end function _M.remove_handler(event, handler) events.remove_handler(event, handler); end function _M.listen_on(port, interface, ssl) addserver(interface or "*", port, listener, "*a", ssl); end function _M.add_host(host) hosts[host] = true; end function _M.remove_host(host) hosts[host] = nil; 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; _M._events = events; return _M; prosody-0.9.1/net/http/codes.lua0000644000175000017500000000374512213321667016505 0ustar matthewmatthew local response_codes = { -- Source: http://www.iana.org/assignments/http-status-codes -- s/^\(\d*\)\s*\(.*\S\)\s*\[RFC.*\]\s*$/^I["\1"] = "\2"; [100] = "Continue"; [101] = "Switching Protocols"; [102] = "Processing"; [200] = "OK"; [201] = "Created"; [202] = "Accepted"; [203] = "Non-Authoritative Information"; [204] = "No Content"; [205] = "Reset Content"; [206] = "Partial Content"; [207] = "Multi-Status"; [208] = "Already Reported"; [226] = "IM Used"; [300] = "Multiple Choices"; [301] = "Moved Permanently"; [302] = "Found"; [303] = "See Other"; [304] = "Not Modified"; [305] = "Use Proxy"; -- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved. [307] = "Temporary Redirect"; [400] = "Bad Request"; [401] = "Unauthorized"; [402] = "Payment Required"; [403] = "Forbidden"; [404] = "Not Found"; [405] = "Method Not Allowed"; [406] = "Not Acceptable"; [407] = "Proxy Authentication Required"; [408] = "Request Timeout"; [409] = "Conflict"; [410] = "Gone"; [411] = "Length Required"; [412] = "Precondition Failed"; [413] = "Request Entity Too Large"; [414] = "Request-URI Too Long"; [415] = "Unsupported Media Type"; [416] = "Requested Range Not Satisfiable"; [417] = "Expectation Failed"; [418] = "I'm a teapot"; [422] = "Unprocessable Entity"; [423] = "Locked"; [424] = "Failed Dependency"; -- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817] [426] = "Upgrade Required"; [500] = "Internal Server Error"; [501] = "Not Implemented"; [502] = "Bad Gateway"; [503] = "Service Unavailable"; [504] = "Gateway Timeout"; [505] = "HTTP Version Not Supported"; [506] = "Variant Also Negotiates"; -- Experimental [507] = "Insufficient Storage"; [508] = "Loop Detected"; [510] = "Not Extended"; }; for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end }) prosody-0.9.1/net/http/parser.lua0000644000175000017500000001247412213321667016703 0ustar matthewmatthewlocal tonumber = tonumber; local assert = assert; local url_parse = require "socket.url".parse; local urldecode = require "util.http".urldecode; local function preprocess_path(path) path = urldecode((path:gsub("//+", "/"))); if path:sub(1,1) ~= "/" then path = "/"..path; end local level = 0; for component in path:gmatch("([^/]+)/") do if component == ".." then level = level - 1; elseif component ~= "." then level = level + 1; end if level < 0 then return nil; end end return path; end local httpstream = {}; 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, chunk_size, chunk_start; local state = nil; local packet; local len; local have_body; local error; return { feed = function(self, data) if error then return nil, "parse has failed"; end if not data then -- EOF if state and client and not len then -- reading client body until EOF packet.body = buf; success_cb(packet); elseif buf ~= "" then -- unexpected EOF error = true; return error_cb(); end return; end buf = buf..data; while #buf > 0 do if state == nil then -- read request local index = buf:find("\r\n\r\n", nil, true); if not index then return; end -- not enough data local method, path, httpversion, status_code, reason_phrase; local first_line; local headers = {}; for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request if first_line then local key, val = line:match("^([^%s:]+): *(.*)$"); if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers key = key:lower(); headers[key] = headers[key] and headers[key]..","..val or val; else 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") or (status_code == 204 or status_code == 304 or status_code == 301) or (status_code >= 100 and status_code < 200) ); else method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); if not method then error = true; return error_cb("invalid-status-line"); end end 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) if not have_body then len = 0; end packet = { code = status_code; httpversion = httpversion; headers = headers; body = have_body and "" or nil; -- COMPAT the properties below are deprecated responseversion = httpversion; responseheaders = headers; }; else local parsed_url; if path:byte() == 47 then -- starts with / local _path, _query = path:match("([^?]*).?(.*)"); if _query == "" then _query = nil; end parsed_url = { path = _path, query = _query }; else parsed_url = url_parse(path); if not(parsed_url and parsed_url.path) then error = true; return error_cb("invalid-url"); end end path = preprocess_path(parsed_url.path); headers.host = parsed_url.host or headers.host; len = len or 0; packet = { method = method; url = parsed_url; path = path; httpversion = httpversion; headers = headers; body = nil; }; end buf = buf:sub(index + 4); state = true; end if state then -- read body if client then if chunked then 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-1)); buf = buf:sub(chunk_start + chunk_size + 2); chunk_size, chunk_start = nil, nil; else -- Partial chunk remaining break; end 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); state = nil; success_cb(packet); else break; end end end end; }; end return httpstream; prosody-0.9.1/net/adns.lua0000644000175000017500000000603412213321667015350 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local server = require "net.server"; local dns = require "net.dns"; local log = require "util.logger".init("adns"); local t_insert, t_remove = table.insert, table.remove; local coroutine, tostring, pcall = coroutine, tostring, pcall; local function dummy_send(sock, data, i, j) return (j-i)+1; end module "adns" function lookup(handler, qname, qtype, qclass) return coroutine.wrap(function (peek) if peek then log("debug", "Records for %s already cached, using those...", qname); handler(peek); return; end log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running())); local ok, err = dns.query(qname, qtype, qclass); if ok then coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); -- Wait for reply log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running())); end if ok then ok, err = pcall(handler, dns.peek(qname, qtype, qclass)); else log("error", "Error sending DNS query: %s", err); ok, err = pcall(handler, nil, err); end if not ok then log("error", "Error in DNS response handler: %s", tostring(err)); end end)(dns.peek(qname, qtype, qclass)); end function cancel(handle, call_handler, reason) log("warn", "Cancelling DNS lookup for %s", tostring(handle[3])); dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler); end function new_async_socket(sock, resolver) local peername = ""; local listener = {}; local handler = {}; function listener.onincoming(conn, data) if data then dns.feed(handler, data); end end function listener.ondisconnect(conn, err) if err then log("warn", "DNS socket for %s disconnected: %s", peername, err); local servers = resolver.server; if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]); end resolver:servfail(conn); -- Let the magic commence end end handler, err = server.wrapclient(sock, "dns", 53, listener); if not handler then return nil, err; end handler.settimeout = function () end handler.setsockname = function (_, ...) return sock:setsockname(...); end handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _:set_send(dummy_send); return ret; end handler.connect = function (_, ...) return sock:connect(...) end --handler.send = function (_, data) _:write(data); return _.sendbuffer and _.sendbuffer(); end handler.send = function (_, data) local getpeername = sock.getpeername; log("debug", "Sending DNS query to %s", (getpeername and getpeername(sock)) or ""); return sock:send(data); end return handler; end dns.socket_wrapper_set(new_async_socket); return _M; prosody-0.9.1/net/dns.lua0000644000175000017500000007121412213321667015211 0ustar matthewmatthew-- Prosody IM -- This file is included with Prosody IM. It has modifications, -- which are hereby placed in the public domain. -- todo: quick (default) header generation -- todo: nxdomain, error handling -- todo: cache results of encodeName -- reference: http://tools.ietf.org/html/rfc1035 -- reference: http://tools.ietf.org/html/rfc1876 (LOC) local socket = require "socket"; local timer = require "util.timer"; local _, windows = pcall(require, "util.windows"); local is_windows = (_ and windows) or os.getenv("WINDIR"); local coroutine, io, math, string, table = coroutine, io, math, string, table; local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type= ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type; local ztact = { -- public domain 20080404 lua@ztact.com get = function(parent, ...) local len = select('#', ...); for i=1,len do parent = parent[select(i, ...)]; if parent == nil then break; end end return parent; end; set = function(parent, ...) local len = select('#', ...); local key, value = select(len-1, ...); local cutpoint, cutkey; for i=1,len-2 do local key = select (i, ...) local child = parent[key] if value == nil then if child == nil then return; elseif next(child, next(child)) then cutpoint = nil; cutkey = nil; elseif cutpoint == nil then cutpoint = parent; cutkey = key; end elseif child == nil then child = {}; parent[key] = child; end parent = child end if value == nil and cutpoint then cutpoint[cutkey] = nil; else parent[key] = value; return value; end end; }; local get, set = ztact.get, ztact.set; local default_timeout = 15; -------------------------------------------------- module dns module('dns') local dns = _M; -- dns type & class codes ------------------------------ dns type & class codes local append = table.insert local function highbyte(i) -- - - - - - - - - - - - - - - - - - - highbyte return (i-(i%0x100))/0x100; end local function augment (t) -- - - - - - - - - - - - - - - - - - - - augment local a = {}; for i,s in pairs(t) do a[i] = s; a[s] = s; a[string.lower(s)] = s; end return a; end local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode local code = {}; for i,s in pairs(t) do local word = string.char(highbyte(i), i%0x100); code[i] = word; code[s] = word; code[string.lower(s)] = word; end return code; end dns.types = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', [ 28] = 'AAAA', [ 29] = 'LOC', [ 33] = 'SRV', [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }; dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }; dns.type = augment (dns.types); dns.class = augment (dns.classes); dns.typecode = encode (dns.types); dns.classcode = encode (dns.classes); local function standardize(qname, qtype, qclass) -- - - - - - - standardize if string.byte(qname, -1) ~= 0x2E then qname = qname..'.'; end qname = string.lower(qname); return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']; end local function prune(rrs, time, soft) -- - - - - - - - - - - - - - - prune time = time or socket.gettime(); for i,rr in pairs(rrs) do if rr.tod then -- rr.tod = rr.tod - 50 -- accelerated decripitude rr.ttl = math.floor(rr.tod - time); if rr.ttl <= 0 then table.remove(rrs, i); return prune(rrs, time, soft); -- Re-iterate end elseif soft == 'soft' then -- What is this? I forget! assert(rr.ttl == 0); rrs[i] = nil; end end end -- metatables & co. ------------------------------------------ metatables & co. local resolver = {}; resolver.__index = resolver; resolver.timeout = default_timeout; local function default_rr_tostring(rr) local rr_val = rr.type and rr[rr.type:lower()]; if type(rr_val) ~= "string" then return ""; end return rr_val; end local special_tostrings = { LOC = resolver.LOC_tostring; MX = function (rr) return string.format('%2i %s', rr.pref, rr.mx); end; SRV = function (rr) local s = rr.srv; return string.format('%5d %5d %5d %s', s.priority, s.weight, s.port, s.target); end; }; local rr_metatable = {}; -- - - - - - - - - - - - - - - - - - - rr_metatable function rr_metatable.__tostring(rr) local rr_string = (special_tostrings[rr.type] or default_rr_tostring)(rr); return string.format('%2s %-5s %6i %-28s %s', rr.class, rr.type, rr.ttl, rr.name, rr_string); end local rrs_metatable = {}; -- - - - - - - - - - - - - - - - - - rrs_metatable function rrs_metatable.__tostring(rrs) local t = {}; for i,rr in pairs(rrs) do append(t, tostring(rr)..'\n'); end return table.concat(t); end local cache_metatable = {}; -- - - - - - - - - - - - - - - - cache_metatable function cache_metatable.__tostring(cache) local time = socket.gettime(); local t = {}; for class,types in pairs(cache) do for type,names in pairs(types) do for name,rrs in pairs(names) do prune(rrs, time); append(t, tostring(rrs)); end end end return table.concat(t); end function resolver:new() -- - - - - - - - - - - - - - - - - - - - - resolver local r = { active = {}, cache = {}, unsorted = {} }; setmetatable(r, resolver); setmetatable(r.cache, cache_metatable); setmetatable(r.unsorted, { __mode = 'kv' }); return r; end -- packet layer -------------------------------------------------- packet layer function dns.random(...) -- - - - - - - - - - - - - - - - - - - dns.random math.randomseed(math.floor(10000*socket.gettime()) % 0x100000000); dns.random = math.random; return dns.random(...); end local function encodeHeader(o) -- - - - - - - - - - - - - - - encodeHeader o = o or {}; o.id = o.id or dns.random(0, 0xffff); -- 16b (random) id o.rd = o.rd or 1; -- 1b 1 recursion desired o.tc = o.tc or 0; -- 1b 1 truncated response o.aa = o.aa or 0; -- 1b 1 authoritative response o.opcode = o.opcode or 0; -- 4b 0 query -- 1 inverse query -- 2 server status request -- 3-15 reserved o.qr = o.qr or 0; -- 1b 0 query, 1 response o.rcode = o.rcode or 0; -- 4b 0 no error -- 1 format error -- 2 server failure -- 3 name error -- 4 not implemented -- 5 refused -- 6-15 reserved o.z = o.z or 0; -- 3b 0 resvered o.ra = o.ra or 0; -- 1b 1 recursion available o.qdcount = o.qdcount or 1; -- 16b number of question RRs o.ancount = o.ancount or 0; -- 16b number of answers RRs o.nscount = o.nscount or 0; -- 16b number of nameservers RRs o.arcount = o.arcount or 0; -- 16b number of additional RRs -- string.char() rounds, so prevent roundup with -0.4999 local header = string.char( highbyte(o.id), o.id %0x100, o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr, o.rcode + 16*o.z + 128*o.ra, highbyte(o.qdcount), o.qdcount %0x100, highbyte(o.ancount), o.ancount %0x100, highbyte(o.nscount), o.nscount %0x100, highbyte(o.arcount), o.arcount %0x100 ); return header, o.id; end local function encodeName(name) -- - - - - - - - - - - - - - - - encodeName local t = {}; for part in string.gmatch(name, '[^.]+') do append(t, string.char(string.len(part))); append(t, part); end append(t, string.char(0)); return table.concat(t); end local function encodeQuestion(qname, qtype, qclass) -- - - - encodeQuestion qname = encodeName(qname); qtype = dns.typecode[qtype or 'a']; qclass = dns.classcode[qclass or 'in']; return qname..qtype..qclass; end function resolver:byte(len) -- - - - - - - - - - - - - - - - - - - - - byte len = len or 1; local offset = self.offset; local last = offset + len - 1; if last > #self.packet then error(string.format('out of bounds: %i>%i', last, #self.packet)); end self.offset = offset + len; return string.byte(self.packet, offset, last); end function resolver:word() -- - - - - - - - - - - - - - - - - - - - - - word local b1, b2 = self:byte(2); return 0x100*b1 + b2; end function resolver:dword () -- - - - - - - - - - - - - - - - - - - - - dword local b1, b2, b3, b4 = self:byte(4); --print('dword', b1, b2, b3, b4); return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4; end function resolver:sub(len) -- - - - - - - - - - - - - - - - - - - - - - sub len = len or 1; local s = string.sub(self.packet, self.offset, self.offset + len - 1); self.offset = self.offset + len; return s; end function resolver:header(force) -- - - - - - - - - - - - - - - - - - header local id = self:word(); --print(string.format(':header id %x', id)); if not self.active[id] and not force then return nil; end local h = { id = id }; local b1, b2 = self:byte(2); h.rd = b1 %2; h.tc = b1 /2%2; h.aa = b1 /4%2; h.opcode = b1 /8%16; h.qr = b1 /128; h.rcode = b2 %16; h.z = b2 /16%8; h.ra = b2 /128; h.qdcount = self:word(); h.ancount = self:word(); h.nscount = self:word(); h.arcount = self:word(); for k,v in pairs(h) do h[k] = v-v%1; end return h; end function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name local remember, pointers = nil, 0; local len = self:byte(); local n = {}; if len == 0 then return "." end -- Root label while len > 0 do if len >= 0xc0 then -- name is "compressed" pointers = pointers + 1; if pointers >= 20 then error('dns error: 20 pointers'); end; local offset = ((len-0xc0)*0x100) + self:byte(); remember = remember or self.offset; self.offset = offset + 1; -- +1 for lua else -- name is not compressed append(n, self:sub(len)..'.'); end len = self:byte(); end self.offset = remember or self.offset; return table.concat(n); end function resolver:question() -- - - - - - - - - - - - - - - - - - question local q = {}; q.name = self:name(); q.type = dns.type[self:word()]; q.class = dns.class[self:word()]; return q; end function resolver:A(rr) -- - - - - - - - - - - - - - - - - - - - - - - - A local b1, b2, b3, b4 = self:byte(4); rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4); end function resolver:AAAA(rr) local addr = {}; for i = 1, rr.rdlength, 2 do local b1, b2 = self:byte(2); table.insert(addr, ("%02x%02x"):format(b1, b2)); end addr = table.concat(addr, ":"):gsub("%f[%x]0+(%x)","%1"); local zeros = {}; for item in addr:gmatch(":[0:]+:") do table.insert(zeros, item) end if #zeros == 0 then rr.aaaa = addr; return elseif #zeros > 1 then table.sort(zeros, function(a, b) return #a > #b end); end rr.aaaa = addr:gsub(zeros[1], "::", 1):gsub("^0::", "::"):gsub("::0$", "::"); end function resolver:CNAME(rr) -- - - - - - - - - - - - - - - - - - - - CNAME rr.cname = self:name(); end function resolver:MX(rr) -- - - - - - - - - - - - - - - - - - - - - - - MX rr.pref = self:word(); rr.mx = self:name(); end function resolver:LOC_nibble_power() -- - - - - - - - - - LOC_nibble_power local b = self:byte(); --print('nibbles', ((b-(b%0x10))/0x10), (b%0x10)); return ((b-(b%0x10))/0x10) * (10^(b%0x10)); end function resolver:LOC(rr) -- - - - - - - - - - - - - - - - - - - - - - LOC rr.version = self:byte(); if rr.version == 0 then rr.loc = rr.loc or {}; rr.loc.size = self:LOC_nibble_power(); rr.loc.horiz_pre = self:LOC_nibble_power(); rr.loc.vert_pre = self:LOC_nibble_power(); rr.loc.latitude = self:dword(); rr.loc.longitude = self:dword(); rr.loc.altitude = self:dword(); end end local function LOC_tostring_degrees(f, pos, neg) -- - - - - - - - - - - - - f = f - 0x80000000; if f < 0 then pos = neg; f = -f; end local deg, min, msec; msec = f%60000; f = (f-msec)/60000; min = f%60; deg = (f-min)/60; return string.format('%3d %2d %2.3f %s', deg, min, msec/1000, pos); end function resolver.LOC_tostring(rr) -- - - - - - - - - - - - - LOC_tostring local t = {}; --[[ for k,name in pairs { 'size', 'horiz_pre', 'vert_pre', 'latitude', 'longitude', 'altitude' } do append(t, string.format('%4s%-10s: %12.0f\n', '', name, rr.loc[name])); end --]] append(t, string.format( '%s %s %.2fm %.2fm %.2fm %.2fm', LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'), LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'), (rr.loc.altitude - 10000000) / 100, rr.loc.size / 100, rr.loc.horiz_pre / 100, rr.loc.vert_pre / 100 )); return table.concat(t); end function resolver:NS(rr) -- - - - - - - - - - - - - - - - - - - - - - - NS rr.ns = self:name(); end function resolver:SOA(rr) -- - - - - - - - - - - - - - - - - - - - - - SOA end function resolver:SRV(rr) -- - - - - - - - - - - - - - - - - - - - - - SRV rr.srv = {}; rr.srv.priority = self:word(); rr.srv.weight = self:word(); rr.srv.port = self:word(); rr.srv.target = self:name(); end function resolver:PTR(rr) rr.ptr = self:name(); end function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT rr.txt = self:sub (self:byte()); end function resolver:rr() -- - - - - - - - - - - - - - - - - - - - - - - - rr local rr = {}; setmetatable(rr, rr_metatable); rr.name = self:name(self); rr.type = dns.type[self:word()] or rr.type; rr.class = dns.class[self:word()] or rr.class; rr.ttl = 0x10000*self:word() + self:word(); rr.rdlength = self:word(); if rr.ttl <= 0 then rr.tod = self.time + 30; else rr.tod = self.time + rr.ttl; end local remember = self.offset; local rr_parser = self[dns.type[rr.type]]; if rr_parser then rr_parser(self, rr); end self.offset = remember; rr.rdata = self:sub(rr.rdlength); return rr; end function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs local rrs = {}; for i = 1,count do append(rrs, self:rr()); end return rrs; end function resolver:decode(packet, force) -- - - - - - - - - - - - - - decode self.packet, self.offset = packet, 1; local header = self:header(force); if not header then return nil; end local response = { header = header }; response.question = {}; local offset = self.offset; for i = 1,response.header.qdcount do append(response.question, self:question()); end response.question.raw = string.sub(self.packet, offset, self.offset - 1); if not force then if not self.active[response.header.id] or not self.active[response.header.id][response.question.raw] then self.active[response.header.id] = nil; return nil; end end response.answer = self:rrs(response.header.ancount); response.authority = self:rrs(response.header.nscount); response.additional = self:rrs(response.header.arcount); return response; end -- socket layer -------------------------------------------------- socket layer resolver.delays = { 1, 3 }; function resolver:addnameserver(address) -- - - - - - - - - - addnameserver self.server = self.server or {}; append(self.server, address); end function resolver:setnameserver(address) -- - - - - - - - - - setnameserver self.server = {}; self:addnameserver(address); end function resolver:adddefaultnameservers() -- - - - - adddefaultnameservers if is_windows then if windows and windows.get_nameservers then for _, server in ipairs(windows.get_nameservers()) do self:addnameserver(server); end end if not self.server or #self.server == 0 then -- TODO log warning about no nameservers, adding opendns servers as fallback self:addnameserver("208.67.222.222"); self:addnameserver("208.67.220.220"); end else -- posix local resolv_conf = io.open("/etc/resolv.conf"); if resolv_conf then for line in resolv_conf:lines() do line = line:gsub("#.*$", "") :match('^%s*nameserver%s+(.*)%s*$'); if line then line:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]", function (address) self:addnameserver(address) end); end end end if not self.server or #self.server == 0 then -- TODO log warning about no nameservers, adding localhost as the default nameserver self:addnameserver("127.0.0.1"); end end end function resolver:getsocket(servernum) -- - - - - - - - - - - - - getsocket self.socket = self.socket or {}; self.socketset = self.socketset or {}; local sock = self.socket[servernum]; if sock then return sock; end local err; sock, err = socket.udp(); if sock and self.socket_wrapper then sock, err = self.socket_wrapper(sock, self); end if not sock then return nil, err; end sock:settimeout(0); -- todo: attempt to use a random port, fallback to 0 sock:setsockname('*', 0); sock:setpeername(self.server[servernum], 53); self.socket[servernum] = sock; self.socketset[sock] = servernum; return sock; end function resolver:voidsocket(sock) if self.socket[sock] then self.socketset[self.socket[sock]] = nil; self.socket[sock] = nil; elseif self.socketset[sock] then self.socket[self.socketset[sock]] = nil; self.socketset[sock] = nil; end sock:close(); end function resolver:socket_wrapper_set(func) -- - - - - - - socket_wrapper_set self.socket_wrapper = func; end function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall for i,sock in ipairs(self.socket) do self.socket[i] = nil; self.socketset[sock] = nil; sock:close(); end end function resolver:remember(rr, type) -- - - - - - - - - - - - - - remember --print ('remember', type, rr.class, rr.type, rr.name) local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class); if type ~= '*' then type = qtype; local all = get(self.cache, qclass, '*', qname); --print('remember all', all); if all then append(all, rr); end end self.cache = self.cache or setmetatable({}, cache_metatable); local rrs = get(self.cache, qclass, type, qname) or set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable)); append(rrs, rr); if type == 'MX' then self.unsorted[rrs] = true; end end local function comp_mx(a, b) -- - - - - - - - - - - - - - - - - - - comp_mx return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref); end function resolver:peek (qname, qtype, qclass) -- - - - - - - - - - - - peek qname, qtype, qclass = standardize(qname, qtype, qclass); local rrs = get(self.cache, qclass, qtype, qname); if not rrs then return nil; end if prune(rrs, socket.gettime()) and qtype == '*' or not next(rrs) then set(self.cache, qclass, qtype, qname, nil); return nil; end if self.unsorted[rrs] then table.sort (rrs, comp_mx); end return rrs; end function resolver:purge(soft) -- - - - - - - - - - - - - - - - - - - purge if soft == 'soft' then self.time = socket.gettime(); for class,types in pairs(self.cache or {}) do for type,names in pairs(types) do for name,rrs in pairs(names) do prune(rrs, self.time, 'soft') end end end else self.cache = setmetatable({}, cache_metatable); end end function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query qname, qtype, qclass = standardize(qname, qtype, qclass) if not self.server then self:adddefaultnameservers(); end local question = encodeQuestion(qname, qtype, qclass); local peek = self:peek (qname, qtype, qclass); if peek then return peek; end local header, id = encodeHeader(); --print ('query id', id, qclass, qtype, qname) local o = { packet = header..question, server = self.best_server, delay = 1, retry = socket.gettime() + self.delays[1] }; -- remember the query self.active[id] = self.active[id] or {}; self.active[id][question] = o; -- remember which coroutine wants the answer local co = coroutine.running(); if co then set(self.wanted, qclass, qtype, qname, co, true); --set(self.yielded, co, qclass, qtype, qname, true); end local conn, err = self:getsocket(o.server) if not conn then return nil, err; end conn:send (o.packet) if timer and self.timeout then local num_servers = #self.server; local i = 1; timer.add_task(self.timeout, function () if get(self.wanted, qclass, qtype, qname, co) then if i < num_servers then i = i + 1; self:servfail(conn); o.server = self.best_server; conn, err = self:getsocket(o.server); if conn then conn:send(o.packet); return self.timeout; end end -- Tried everything, failed self:cancel(qclass, qtype, qname, co, true); end end) end return true; end function resolver:servfail(sock) -- Resend all queries for this server local num = self.socketset[sock] -- Socket is dead now self:voidsocket(sock); -- Find all requests to the down server, and retry on the next server self.time = socket.gettime(); for id,queries in pairs(self.active) do for question,o in pairs(queries) do if o.server == num then -- This request was to the broken server o.server = o.server + 1 -- Use next server if o.server > #self.server then o.server = 1; end o.retries = (o.retries or 0) + 1; if o.retries >= #self.server then --print('timeout'); queries[question] = nil; else local _a = self:getsocket(o.server); if _a then _a:send(o.packet); end end end end if next(queries) == nil then self.active[id] = nil; end end if num == self.best_server then self.best_server = self.best_server + 1; if self.best_server > #self.server then -- Exhausted all servers, try first again self.best_server = 1; end end end function resolver:settimeout(seconds) self.timeout = seconds; end function resolver:receive(rset) -- - - - - - - - - - - - - - - - - receive --print('receive'); print(self.socket); self.time = socket.gettime(); rset = rset or self.socket; local response; for i,sock in pairs(rset) do if self.socketset[sock] then local packet = sock:receive(); if packet then response = self:decode(packet); if response and self.active[response.header.id] and self.active[response.header.id][response.question.raw] then --print('received response'); --self.print(response); for j,rr in pairs(response.answer) do if rr.name:sub(-#response.question[1].name, -1) == response.question[1].name then self:remember(rr, response.question[1].type) end end -- retire the query local queries = self.active[response.header.id]; queries[response.question.raw] = nil; if not next(queries) then self.active[response.header.id] = nil; end if not next(self.active) then self:closeall(); end -- was the query on the wanted list? local q = response.question[1]; local cos = get(self.wanted, q.class, q.type, q.name); if cos then for co in pairs(cos) do set(self.yielded, co, q.class, q.type, q.name, nil); if coroutine.status(co) == "suspended" then coroutine.resume(co); end end set(self.wanted, q.class, q.type, q.name, nil); end end end end end return response; end function resolver:feed(sock, packet, force) --print('receive'); print(self.socket); self.time = socket.gettime(); local response = self:decode(packet, force); if response and self.active[response.header.id] and self.active[response.header.id][response.question.raw] then --print('received response'); --self.print(response); for j,rr in pairs(response.answer) do self:remember(rr, response.question[1].type); end -- retire the query local queries = self.active[response.header.id]; queries[response.question.raw] = nil; if not next(queries) then self.active[response.header.id] = nil; end if not next(self.active) then self:closeall(); end -- was the query on the wanted list? local q = response.question[1]; if q then local cos = get(self.wanted, q.class, q.type, q.name); if cos then for co in pairs(cos) do set(self.yielded, co, q.class, q.type, q.name, nil); if coroutine.status(co) == "suspended" then coroutine.resume(co); end end set(self.wanted, q.class, q.type, q.name, nil); end end end return response; end function resolver:cancel(qclass, qtype, qname, co, call_handler) local cos = get(self.wanted, qclass, qtype, qname); if cos then if call_handler then coroutine.resume(co); end cos[co] = nil; end end function resolver:pulse() -- - - - - - - - - - - - - - - - - - - - - pulse --print(':pulse'); while self:receive() do end if not next(self.active) then return nil; end self.time = socket.gettime(); for id,queries in pairs(self.active) do for question,o in pairs(queries) do if self.time >= o.retry then o.server = o.server + 1; if o.server > #self.server then o.server = 1; o.delay = o.delay + 1; end if o.delay > #self.delays then --print('timeout'); queries[question] = nil; if not next(queries) then self.active[id] = nil; end if not next(self.active) then return nil; end else --print('retry', o.server, o.delay); local _a = self.socket[o.server]; if _a then _a:send(o.packet); end o.retry = self.time + self.delays[o.delay]; end end end end if next(self.active) then return true; end return nil; end function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup self:query (qname, qtype, qclass) while self:pulse() do local recvt = {} for i, s in ipairs(self.socket) do recvt[i] = s end socket.select(recvt, nil, 4) end --print(self.cache); return self:peek(qname, qtype, qclass); end function resolver:lookupex(handler, qname, qtype, qclass) -- - - - - - - - - - lookup return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass); end function resolver:tohostname(ip) return dns.lookup(ip:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)", "%4.%3.%2.%1.in-addr.arpa."), "PTR"); end --print ---------------------------------------------------------------- print local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints qr = { [0]='query', 'response' }, opcode = { [0]='query', 'inverse query', 'server status request' }, aa = { [0]='non-authoritative', 'authoritative' }, tc = { [0]='complete', 'truncated' }, rd = { [0]='recursion not desired', 'recursion desired' }, ra = { [0]='recursion not available', 'recursion available' }, z = { [0]='(reserved)' }, rcode = { [0]='no error', 'format error', 'server failure', 'name error', 'not implemented' }, type = dns.type, class = dns.class }; local function hint(p, s) -- - - - - - - - - - - - - - - - - - - - - - hint return (hints[s] and hints[s][p[s]]) or ''; end function resolver.print(response) -- - - - - - - - - - - - - resolver.print for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) ); end for i,question in ipairs(response.question) do print(string.format ('question[%i].name ', i), question.name); print(string.format ('question[%i].type ', i), question.type); print(string.format ('question[%i].class ', i), question.class); end local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }; local tmp; for s,s in pairs({'answer', 'authority', 'additional'}) do for i,rr in pairs(response[s]) do for j,t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do tmp = string.format('%s[%i].%s', s, i, t); print(string.format('%-30s', tmp), rr[t], hint(rr, t)); end for j,t in pairs(rr) do if not common[j] then tmp = string.format('%s[%i].%s', s, i, j); print(string.format('%-30s %s', tostring(tmp), tostring(t))); end end end end end -- module api ------------------------------------------------------ module api function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver -- this function seems to be redundant with resolver.new () local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {}, best_server = 1 }; setmetatable (r, resolver); setmetatable (r.cache, cache_metatable); setmetatable (r.unsorted, { __mode = 'kv' }); return r; end local _resolver = dns.resolver(); dns._resolver = _resolver; function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup return _resolver:lookup(...); end function dns.tohostname(...) return _resolver:tohostname(...); end function dns.purge(...) -- - - - - - - - - - - - - - - - - - - - - - purge return _resolver:purge(...); end function dns.peek(...) -- - - - - - - - - - - - - - - - - - - - - - - peek return _resolver:peek(...); end function dns.query(...) -- - - - - - - - - - - - - - - - - - - - - - query return _resolver:query(...); end function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - - feed return _resolver:feed(...); end function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel return _resolver:cancel(...); end function dns.settimeout(...) return _resolver:settimeout(...); end function dns.cache() return _resolver.cache; end function dns.socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set return _resolver:socket_wrapper_set(...); end return dns; prosody-0.9.1/net/http.lua0000644000175000017500000001204412213321667015400 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local socket = require "socket" local b64 = require "util.encodings".base64.encode; local url = require "socket.url" local httpstream_new = require "net.http.parser".new; local util_http = require "util.http"; local ssl_available = pcall(require, "ssl"); local server = require "net.server" local t_insert, t_concat = table.insert, table.concat; local pairs = pairs; local tonumber, tostring, xpcall, select, traceback = tonumber, tostring, xpcall, select, debug.traceback; local log = require "util.logger".init("http"); module "http" local requests = {}; -- Open requests local listener = { default_port = 80, default_mode = "*a" }; function listener.onconnect(conn) local req = requests[conn]; -- Send the request local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" }; if req.query then t_insert(request_line, 4, "?"..req.query); end conn:write(t_concat(request_line)); local t = { [2] = ": ", [4] = "\r\n" }; for k, v in pairs(req.headers) do t[1], t[3] = k, v; conn:write(t_concat(t)); end conn:write("\r\n"); if req.body then conn:write(req.body); end end function listener.onincoming(conn, data) local request = requests[conn]; if not request then log("warn", "Received response from connection %s with no request attached!", tostring(conn)); return; end if data and request.reader then request:reader(data); end end function listener.ondisconnect(conn, err) local request = requests[conn]; if request and request.conn then request:reader(nil, err); end requests[conn] = nil; end local function request_reader(request, data, err) if not request.parser then local function error_cb(reason) if request.callback then request.callback(reason or "connection-closed", 0, request); request.callback = nil; end destroy_request(request); end if not data then error_cb(err); return; end local function success_cb(r) if request.callback then request.callback(r.body, r.code, r, request); request.callback = nil; end destroy_request(request); end local function options_cb() return request; end request.parser = httpstream_new(success_cb, error_cb, "client", options_cb); end request.parser:feed(data); 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); if not (req and req.host) then callback(nil, 0, req); return nil, "invalid-url"; end if not req.path then req.path = "/"; end local method, headers, body; local host, port = req.host, req.port; local host_header = host; if (port == "80" and req.scheme == "http") or (port == "443" and req.scheme == "https") then port = nil; elseif port then host_header = host_header..":"..port; end headers = { ["Host"] = host_header; ["User-Agent"] = "Prosody XMPP Server"; }; if req.userinfo then headers["Authorization"] = "Basic "..b64(req.userinfo); end if ex then req.onlystatus = ex.onlystatus; body = ex.body; if body then method = "POST"; headers["Content-Length"] = tostring(#body); headers["Content-Type"] = "application/x-www-form-urlencoded"; end if ex.method then method = ex.method; end if ex.headers then for k, v in pairs(ex.headers) do headers[k] = v; end end end -- Attach to request object 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_number = port and tonumber(port) or (using_https and 443 or 80); -- Connect the socket, and wrap it with net.server local conn = socket.tcp(); conn:settimeout(10); local ok, err = conn:connect(host, port_number); if not ok and err ~= "timeout" then callback(nil, 0, req); return nil, err; end local sslctx = false; if using_https then sslctx = ex and ex.sslctx or { mode = "client", protocol = "sslv23", options = { "no_sslv2" } }; end req.handler, req.conn = server.wrapclient(conn, host, port_number, listener, "*a", sslctx); req.write = function (...) return req.handler:write(...); end req.callback = function (content, code, request, response) log("debug", "Calling callback, status %s", code or "---"); return select(2, xpcall(function () return callback(content, code, request, response) end, handleerr)); end req.reader = request_reader; req.state = "status"; requests[req.handler] = req; return req; end function destroy_request(request) if request.conn then request.conn = nil; request.handler:close() end end 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; prosody-0.9.1/net/connlisteners.lua0000644000175000017500000000065712213321667017316 0ustar matthewmatthew-- COMPAT w/pre-0.9 local log = require "util.logger".init("net.connlisteners"); local traceback = debug.traceback; module "httpserver" function fail() log("error", "Attempt to use legacy connlisteners API. For more info see http://prosody.im/doc/developers/network"); log("error", "Legacy connlisteners API usage, %s", traceback("", 2)); end register, deregister = fail, fail; get, start = fail, fail, epic_fail; return _M; prosody-0.9.1/net/httpserver.lua0000644000175000017500000000062612213321667016632 0ustar matthewmatthew-- COMPAT w/pre-0.9 local log = require "util.logger".init("net.httpserver"); local traceback = debug.traceback; module "httpserver" function fail() log("error", "Attempt to use legacy HTTP API. For more info see http://prosody.im/doc/developers/legacy_http"); log("error", "Legacy HTTP API usage, %s", traceback("", 2)); end new, new_from_config = fail, fail; set_default_handler = fail; return _M; prosody-0.9.1/COPYING0000644000175000017500000000211312213321667014157 0ustar matthewmatthewCopyright (c) 2008-2011 Matthew Wild Copyright (c) 2008-2011 Waqas Hussain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prosody-0.9.1/prosody.cfg.lua.dist0000644000175000017500000001677612213321667017052 0ustar matthewmatthew-- Prosody Example Configuration File -- -- Information on configuring Prosody can be found on our -- website at http://prosody.im/doc/configure -- -- Tip: You can check that the syntax of this file is correct -- when you have finished by running: luac -p prosody.cfg.lua -- If there are any errors, it will let you know what and where -- they are, otherwise it will keep quiet. -- -- The only thing left to do is rename this file to remove the .dist ending, and fill in the -- blanks. Good luck, and happy Jabbering! ---------- Server-wide settings ---------- -- Settings in this section apply to the whole server and are the default settings -- for any virtual hosts -- This is a (by default, empty) list of accounts that are admins -- for the server. Note that you must create the accounts separately -- (see http://prosody.im/doc/creating_accounts for info) -- Example: admins = { "user1@example.com", "user2@example.net" } admins = { } -- Enable use of libevent for better performance under high load -- For more information see: http://prosody.im/doc/libevent --use_libevent = true; -- This is the list of modules Prosody will load on startup. -- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too. -- Documentation on modules can be found at: http://prosody.im/doc/modules modules_enabled = { -- Generally required "roster"; -- Allow users to have a roster. Recommended ;) "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. "tls"; -- Add support for secure TLS on c2s/s2s connections "dialback"; -- s2s dialback support "disco"; -- Service discovery -- Not essential, but recommended "private"; -- Private XML storage (for room bookmarks, etc.) "vcard"; -- Allow users to set vCards -- These are commented by default as they have a performance impact --"privacy"; -- Support privacy lists --"compression"; -- Stream compression -- Nice to have "version"; -- Replies to server version requests "uptime"; -- Report how long server has been running "time"; -- Let others know the time here on this server "ping"; -- Replies to XMPP pings with pongs "pep"; -- Enables users to publish their mood, activity, playing music and more "register"; -- Allow users to register on this server using a client and change passwords -- Admin interfaces "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands --"admin_telnet"; -- Opens telnet console interface on localhost port 5582 -- HTTP modules --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" --"http_files"; -- Serve static files from a directory over HTTP -- Other specific functionality --"posix"; -- POSIX functionality, sends server to background, enables syslog, etc. --"groups"; -- Shared roster support --"announce"; -- Send announcement to all online users --"welcome"; -- Welcome users who register accounts --"watchregistrations"; -- Alert admins of registrations --"motd"; -- Send a message to users when they log in --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. }; -- These modules are auto-loaded, but should you want -- to disable them then uncomment them here: modules_disabled = { -- "offline"; -- Store offline messages -- "c2s"; -- Handle client connections -- "s2s"; -- Handle server-to-server connections }; -- Disable account creation by default, for security -- For more information see http://prosody.im/doc/creating_accounts allow_registration = false; -- These are the SSL/TLS-related settings. If you don't want -- to use SSL/TLS, you may comment or remove this ssl = { key = "certs/localhost.key"; certificate = "certs/localhost.crt"; } -- Force clients to use encrypted connections? This option will -- prevent clients from authenticating unless they are using encryption. c2s_require_encryption = false -- Force certificate authentication for server-to-server connections? -- This provides ideal security, but requires servers you communicate -- with to support encryption AND present valid, trusted certificates. -- NOTE: Your version of LuaSec must support certificate verification! -- For more information see http://prosody.im/doc/s2s#security s2s_secure_auth = false -- Many servers don't support encryption or have invalid or self-signed -- certificates. You can list domains here that will not be required to -- authenticate using certificates. They will be authenticated using DNS. --s2s_insecure_domains = { "gmail.com" } -- Even if you leave s2s_secure_auth disabled, you can still require valid -- certificates for some domains by specifying a list here. --s2s_secure_domains = { "jabber.org" } -- Select the authentication backend to use. The 'internal' providers -- use Prosody's configured data storage to store the authentication data. -- To allow Prosody to offer secure authentication mechanisms to clients, the -- default provider stores passwords in plaintext. If you do not trust your -- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed -- for information about using the hashed backend. authentication = "internal_plain" -- Select the storage backend to use. By default Prosody uses flat files -- in its configured data directory, but it also supports more backends -- through modules. An "sql" backend is included by default, but requires -- additional dependencies. See http://prosody.im/doc/storage for more info. --storage = "sql" -- Default is "internal" -- For the "sql" backend, you can uncomment *one* of the below to configure: --sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. --sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } --sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } -- Logging configuration -- For advanced logging see http://prosody.im/doc/logging log = { info = "prosody.log"; -- Change 'info' to 'debug' for verbose logging error = "prosody.err"; -- "*syslog"; -- Uncomment this for logging to syslog -- "*console"; -- Log to the console, useful for debugging with daemonize=false } ----------- Virtual hosts ----------- -- You need to add a VirtualHost entry for each domain you wish Prosody to serve. -- Settings under each VirtualHost entry apply *only* to that host. VirtualHost "localhost" VirtualHost "example.com" enabled = false -- Remove this line to enable this host -- Assign this host a certificate for TLS, otherwise it would use the one -- set in the global section (if any). -- Note that old-style SSL on port 5223 only supports one certificate, and will always -- use the global one. ssl = { key = "certs/example.com.key"; certificate = "certs/example.com.crt"; } ------ Components ------ -- You can specify components to add hosts that provide special services, -- like multi-user conferences, and transports. -- For more information on components, see http://prosody.im/doc/components ---Set up a MUC (multi-user chat) room server on conference.example.com: --Component "conference.example.com" "muc" -- Set up a SOCKS5 bytestream proxy for server-proxied file transfers: --Component "proxy.example.com" "proxy65" ---Set up an external component (default component port is 5347) -- -- External components allow adding various services, such as gateways/ -- transports to other networks like ICQ, MSN and Yahoo. For more info -- see: http://prosody.im/doc/components#adding_an_external_component -- --Component "gateway.example.com" -- component_secret = "password" prosody-0.9.1/HACKERS0000644000175000017500000000072612213321667014137 0ustar matthewmatthewWelcome hackers! This project accepts and *encourages* contributions. If you would like to get involved you can join us on our mailing list and discussion rooms. More information on these at http://prosody.im/discuss Patches are welcome, though before sending we would appreciate if you read docs/coding_style.txt for guidelines on how to format your code, and other tips. Documentation for developers can be found at http://prosody.im/doc/developers Have fun :) prosody-0.9.1/prosody0000755000175000017500000002744512213321667014570 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- prosody - main executable for Prosody XMPP server -- Will be modified by configure script if run -- CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR"); CFG_DATADIR=os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- local function is_relative(path) local path_sep = package.config:sub(1,1); return ((path_sep == "/" and path:sub(1,1) ~= "/") or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end -- Tell Lua where to find our libraries if CFG_SOURCEDIR then local function filter_relative_paths(path) if is_relative(path) then return ""; end end local function sanitise_paths(paths) return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); end package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); end -- Substitute ~ with path to home directory in data path if CFG_DATADIR then if os.getenv("HOME") then CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); end end -- Global 'prosody' object local prosody = { events = require "util.events".new(); }; _G.prosody = prosody; -- Check dependencies local dependencies = require "util.dependencies"; if not dependencies.check_dependencies() then os.exit(1); end -- Load the config-parsing module config = require "core.configmanager" -- -- -- -- -- Define the functions we call during startup, the -- actual startup happens right at the end, where these -- functions get called function read_config() local filenames = {}; local filename; if arg[1] == "--config" and arg[2] then table.insert(filenames, arg[2]); if CFG_CONFIGDIR then table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]); end elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl table.insert(filenames, os.getenv("PROSODY_CONFIG")); else for _, format in ipairs(config.parsers()) do table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format); end end for _,_filename in ipairs(filenames) do filename = _filename; local file = io.open(filename); if file then file:close(); CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$"); break; end end local ok, level, err = config.load(filename); if not ok then print("\n"); print("**************************"); if level == "parser" then print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"..":"); print(""); local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)"); if err:match("chunk has too many syntax levels$") then print("An Include statement in a config file is including an already-included"); print("file and causing an infinite loop. An Include statement in a config file is..."); else print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err))); end print(""); elseif level == "file" then print("Prosody was unable to find the configuration file."); print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist"); print("Copy or rename it to prosody.cfg.lua and edit as necessary."); end print("More help on configuring Prosody can be found at http://prosody.im/doc/configure"); print("Good luck!"); print("**************************"); print(""); os.exit(1); end end function load_libraries() -- Load socket framework server = require "net.server" end function init_logging() -- Initialize logging require "core.loggingmanager" end function log_dependency_warnings() dependencies.log_warnings(); end function sanity_check() for host, host_config in pairs(config.getconfig()) do if host ~= "*" and host_config.enabled ~= false and not host_config.component_module then return; end end log("error", "No enabled VirtualHost entries found in the config file."); log("error", "At least one active host is required for Prosody to function. Exiting..."); os.exit(1); end function sandbox_require() -- Replace require() with one that doesn't pollute _G, required -- for neat sandboxing of modules local _realG = _G; local _real_require = require; if not getfenv then -- FIXME: This is a hack to replace getfenv() in Lua 5.2 function getfenv(f) return debug.getupvalue(debug.getinfo(f or 1).func, 1); end end function require(...) local curr_env = getfenv(2); local curr_env_mt = getmetatable(curr_env); local _realG_mt = getmetatable(_realG); if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then local old_newindex, old_index; old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env; old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) return rawget(curr_env, k); end; local ret = _real_require(...); _realG_mt.__newindex = old_newindex; _realG_mt.__index = old_index; return ret; end return _real_require(...); end end function set_function_metatable() local mt = {}; function mt.__index(f, upvalue) local i, name, value = 0; repeat i = i + 1; name, value = debug.getupvalue(f, i); until name == upvalue or name == nil; return value; end function mt.__newindex(f, upvalue, value) local i, name = 0; repeat i = i + 1; name = debug.getupvalue(f, i); until name == upvalue or name == nil; if name then debug.setupvalue(f, i, value); end end function mt.__tostring(f) local info = debug.getinfo(f); return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined); end debug.setmetatable(function() end, mt); end function init_global_state() -- COMPAT: These globals are deprecated bare_sessions = {}; full_sessions = {}; hosts = {}; prosody.bare_sessions = bare_sessions; prosody.full_sessions = full_sessions; prosody.hosts = hosts; local data_path = config.get("*", "data_path") or CFG_DATADIR or "data"; local custom_plugin_paths = config.get("*", "plugin_paths"); if custom_plugin_paths then local path_sep = package.config:sub(3,3); -- path1;path2;path3;defaultpath... CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); end prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".", plugins = CFG_PLUGINDIR or "plugins", data = data_path }; prosody.arg = _G.arg; prosody.platform = "unknown"; if os.getenv("WINDIR") then prosody.platform = "windows"; elseif package.config:sub(1,1) == "/" then prosody.platform = "posix"; end prosody.installed = nil; if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then prosody.installed = true; end if prosody.installed then -- Change working directory to data path. require "lfs".chdir(data_path); end -- Function to reload the config file function prosody.reload_config() log("info", "Reloading configuration file"); prosody.events.fire_event("reloading-config"); local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua"); if not ok then if level == "parser" then log("error", "There was an error parsing the configuration file: %s", tostring(err)); elseif level == "file" then log("error", "Couldn't read the config file when trying to reload: %s", tostring(err)); end end return ok, (err and tostring(level)..": "..tostring(err)) or nil; end -- Function to reopen logfiles function prosody.reopen_logfiles() log("info", "Re-opening log files"); prosody.events.fire_event("reopen-log-files"); end -- Function to initiate prosody shutdown function prosody.shutdown(reason) log("info", "Shutting down: %s", reason or "unknown reason"); prosody.shutdown_reason = reason; prosody.events.fire_event("server-stopping", {reason = reason}); server.setquitting(true); end -- Load SSL settings from config, and create a ctx table local certmanager = require "core.certmanager"; local global_ssl_ctx = certmanager.create_context("*", "server"); prosody.global_ssl_ctx = global_ssl_ctx; end function read_version() -- Try to determine version local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version"); if version_file then prosody.version = version_file:read("*a"):gsub("%s*$", ""); version_file:close(); if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then prosody.version = "hg:"..prosody.version; end else prosody.version = "unknown"; end end function load_secondary_libraries() --- Load and initialise core modules require "util.import" require "util.xmppstream" require "core.stanza_router" require "core.hostmanager" require "core.portmanager" require "core.modulemanager" require "core.usermanager" require "core.rostermanager" require "core.sessionmanager" package.loaded['core.componentmanager'] = setmetatable({},{__index=function() log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)")); return function() end end}); require "net.http" require "util.array" require "util.datetime" require "util.iterators" require "util.timer" require "util.helpers" pcall(require, "util.signal") -- Not on Windows -- Commented to protect us from -- the second kind of people --[[ pcall(require, "remdebug.engine"); if remdebug then remdebug.engine.start() end ]] require "util.stanza" require "util.jid" end function init_data_store() require "core.storagemanager"; end function prepare_to_start() log("info", "Prosody is using the %s backend for connection handling", server.get_backend()); -- Signal to modules that we are ready to start prosody.events.fire_event("server-starting"); prosody.start_time = os.time(); end function init_global_protection() -- Catch global accesses local locked_globals_mt = { __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end; __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end; }; function prosody.unlock_globals() setmetatable(_G, nil); end function prosody.lock_globals() setmetatable(_G, locked_globals_mt); end -- And lock now... prosody.lock_globals(); end function loop() -- Error handler for errors that make it this far local function catch_uncaught_error(err) if type(err) == "string" and err:match("interrupted!$") then return "quitting"; end log("error", "Top-level error, please report:\n%s", tostring(err)); local traceback = debug.traceback("", 2); if traceback then log("error", "%s", traceback); end prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback}); end while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do socket.sleep(0.2); end end function cleanup() log("info", "Shutdown status: Cleaning up"); prosody.events.fire_event("server-cleanup"); end -- Are you ready? :) -- These actions are in a strict order, as many depend on -- previous steps to have already been performed read_config(); init_logging(); sanity_check(); sandbox_require(); set_function_metatable(); load_libraries(); init_global_state(); read_version(); log("info", "Hello and welcome to Prosody version %s", prosody.version); log_dependency_warnings(); load_secondary_libraries(); init_data_store(); init_global_protection(); prepare_to_start(); prosody.events.fire_event("server-started"); loop(); log("info", "Shutting down..."); cleanup(); prosody.events.fire_event("server-stopped"); log("info", "Shutdown complete"); prosody-0.9.1/util-src/0000775000175000017500000000000012213321667014673 5ustar matthewmatthewprosody-0.9.1/util-src/make.bat0000644000175000017500000000004112213321667016271 0ustar matthewmatthew@nmake /nologo /f Makefile.win %*prosody-0.9.1/util-src/net.c0000644000175000017500000000517412213321667015632 0ustar matthewmatthew/* Prosody IM -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- Copyright (C) 2012 Paul Aurich -- Copyright (C) 2013 Matthew Wild -- Copyright (C) 2013 Florian Zeitz -- */ #include #include #include #ifndef _WIN32 #include #include #include #include #include #include #include #endif #include #include /* Enumerate all locally configured IP addresses */ const char * const type_strings[] = { "both", "ipv4", "ipv6", NULL }; static int lc_local_addresses(lua_State *L) { #ifndef _WIN32 /* Link-local IPv4 addresses; see RFC 3927 and RFC 5735 */ const long ip4_linklocal = htonl(0xa9fe0000); /* 169.254.0.0 */ const long ip4_mask = htonl(0xffff0000); struct ifaddrs *addr = NULL, *a; #endif int n = 1; int type = luaL_checkoption(L, 1, "both", type_strings); const char link_local = lua_toboolean(L, 2); /* defaults to 0 (false) */ const char ipv4 = (type == 0 || type == 1); const char ipv6 = (type == 0 || type == 2); #ifndef _WIN32 if (getifaddrs(&addr) < 0) { lua_pushnil(L); lua_pushfstring(L, "getifaddrs failed (%d): %s", errno, strerror(errno)); return 2; } #endif lua_newtable(L); #ifndef _WIN32 for (a = addr; a; a = a->ifa_next) { int family; char ipaddr[INET6_ADDRSTRLEN]; const char *tmp = NULL; if (a->ifa_addr == NULL || a->ifa_flags & IFF_LOOPBACK) continue; family = a->ifa_addr->sa_family; if (ipv4 && family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)a->ifa_addr; if (!link_local &&((sa->sin_addr.s_addr & ip4_mask) == ip4_linklocal)) continue; tmp = inet_ntop(family, &sa->sin_addr, ipaddr, sizeof(ipaddr)); } else if (ipv6 && family == AF_INET6) { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)a->ifa_addr; if (!link_local && IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) continue; if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&sa->sin6_addr)) continue; tmp = inet_ntop(family, &sa->sin6_addr, ipaddr, sizeof(ipaddr)); } if (tmp != NULL) { lua_pushstring(L, tmp); lua_rawseti(L, -2, n++); } /* TODO: Error reporting? */ } freeifaddrs(addr); #else if (ipv4) { lua_pushstring(L, "0.0.0.0"); lua_rawseti(L, -2, n++); } if (ipv6) { lua_pushstring(L, "::"); lua_rawseti(L, -2, n++); } #endif return 1; } int luaopen_util_net(lua_State* L) { luaL_Reg exports[] = { { "local_addresses", lc_local_addresses }, { NULL, NULL } }; luaL_register(L, "net", exports); return 1; } prosody-0.9.1/util-src/hashes.c0000644000175000017500000001315512213321667016315 0ustar matthewmatthew/* Prosody IM -- Copyright (C) 2009-2010 Matthew Wild -- Copyright (C) 2009-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * hashes.c * Lua library for sha1, sha256 and md5 hashes */ #include #include #ifdef _MSC_VER typedef unsigned __int32 uint32_t; #else #include #endif #include "lua.h" #include "lauxlib.h" #include #include #define HMAC_IPAD 0x36363636 #define HMAC_OPAD 0x5c5c5c5c const char *hex_tab = "0123456789abcdef"; void toHex(const unsigned char *in, int length, unsigned char *out) { int i; for (i = 0; i < length; i++) { out[i*2] = hex_tab[(in[i] >> 4) & 0xF]; out[i*2+1] = hex_tab[(in[i]) & 0xF]; } } #define MAKE_HASH_FUNCTION(myFunc, func, size) \ static int myFunc(lua_State *L) { \ size_t len; \ const char *s = luaL_checklstring(L, 1, &len); \ int hex_out = lua_toboolean(L, 2); \ unsigned char hash[size], result[size*2]; \ func((const unsigned char*)s, len, hash); \ if (hex_out) { \ toHex(hash, size, result); \ lua_pushlstring(L, (char*)result, size*2); \ } else { \ lua_pushlstring(L, (char*)hash, size);\ } \ return 1; \ } MAKE_HASH_FUNCTION(Lsha1, SHA1, SHA_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha224, SHA224, SHA224_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha256, SHA256, SHA256_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha384, SHA384, SHA384_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lsha512, SHA512, SHA512_DIGEST_LENGTH) MAKE_HASH_FUNCTION(Lmd5, MD5, MD5_DIGEST_LENGTH) struct hash_desc { int (*Init)(void*); int (*Update)(void*, const void *, size_t); int (*Final)(unsigned char*, void*); size_t digestLength; void *ctx, *ctxo; }; static void hmac(struct hash_desc *desc, const char *key, size_t key_len, const char *msg, size_t msg_len, unsigned char *result) { union xory { unsigned char bytes[64]; uint32_t quadbytes[16]; }; int i; unsigned char hashedKey[64]; /* Maximum used digest length */ union xory k_ipad, k_opad; if (key_len > 64) { desc->Init(desc->ctx); desc->Update(desc->ctx, key, key_len); desc->Final(hashedKey, desc->ctx); key = (const char*)hashedKey; key_len = desc->digestLength; } memcpy(k_ipad.bytes, key, key_len); memset(k_ipad.bytes + key_len, 0, 64 - key_len); memcpy(k_opad.bytes, k_ipad.bytes, 64); for (i = 0; i < 16; i++) { k_ipad.quadbytes[i] ^= HMAC_IPAD; k_opad.quadbytes[i] ^= HMAC_OPAD; } desc->Init(desc->ctx); desc->Update(desc->ctx, k_ipad.bytes, 64); desc->Init(desc->ctxo); desc->Update(desc->ctxo, k_opad.bytes, 64); desc->Update(desc->ctx, msg, msg_len); desc->Final(result, desc->ctx); desc->Update(desc->ctxo, result, desc->digestLength); desc->Final(result, desc->ctxo); } #define MAKE_HMAC_FUNCTION(myFunc, func, size, type) \ static int myFunc(lua_State *L) { \ type ctx, ctxo; \ unsigned char hash[size], result[2*size]; \ size_t key_len, msg_len; \ const char *key = luaL_checklstring(L, 1, &key_len); \ const char *msg = luaL_checklstring(L, 2, &msg_len); \ const int hex_out = lua_toboolean(L, 3); \ struct hash_desc desc; \ desc.Init = (int (*)(void*))func##_Init; \ desc.Update = (int (*)(void*, const void *, size_t))func##_Update; \ desc.Final = (int (*)(unsigned char*, void*))func##_Final; \ desc.digestLength = size; \ desc.ctx = &ctx; \ desc.ctxo = &ctxo; \ hmac(&desc, key, key_len, msg, msg_len, hash); \ if (hex_out) { \ toHex(hash, size, result); \ lua_pushlstring(L, (char*)result, size*2); \ } else { \ lua_pushlstring(L, (char*)hash, size); \ } \ return 1; \ } MAKE_HMAC_FUNCTION(Lhmac_sha1, SHA1, SHA_DIGEST_LENGTH, SHA_CTX) MAKE_HMAC_FUNCTION(Lhmac_sha256, SHA256, SHA256_DIGEST_LENGTH, SHA256_CTX) MAKE_HMAC_FUNCTION(Lhmac_sha512, SHA512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, MD5, MD5_DIGEST_LENGTH, MD5_CTX) static int LscramHi(lua_State *L) { union xory { unsigned char bytes[SHA_DIGEST_LENGTH]; uint32_t quadbytes[SHA_DIGEST_LENGTH/4]; }; int i; SHA_CTX ctx, ctxo; unsigned char Ust[SHA_DIGEST_LENGTH]; union xory Und; union xory res; size_t str_len, salt_len; struct hash_desc desc; const char *str = luaL_checklstring(L, 1, &str_len); const char *salt = luaL_checklstring(L, 2, &salt_len); char *salt2; const int iter = luaL_checkinteger(L, 3); desc.Init = (int (*)(void*))SHA1_Init; desc.Update = (int (*)(void*, const void *, size_t))SHA1_Update; desc.Final = (int (*)(unsigned char*, void*))SHA1_Final; desc.digestLength = SHA_DIGEST_LENGTH; desc.ctx = &ctx; desc.ctxo = &ctxo; salt2 = malloc(salt_len + 4); if (salt2 == NULL) luaL_error(L, "Out of memory in scramHi"); memcpy(salt2, salt, salt_len); memcpy(salt2 + salt_len, "\0\0\0\1", 4); hmac(&desc, str, str_len, salt2, salt_len + 4, Ust); free(salt2); memcpy(res.bytes, Ust, sizeof(res)); for (i = 1; i < iter; i++) { int j; hmac(&desc, str, str_len, (char*)Ust, sizeof(Ust), Und.bytes); for (j = 0; j < SHA_DIGEST_LENGTH/4; j++) res.quadbytes[j] ^= Und.quadbytes[j]; memcpy(Ust, Und.bytes, sizeof(Ust)); } lua_pushlstring(L, (char*)res.bytes, SHA_DIGEST_LENGTH); return 1; } static const luaL_Reg Reg[] = { { "sha1", Lsha1 }, { "sha224", Lsha224 }, { "sha256", Lsha256 }, { "sha384", Lsha384 }, { "sha512", Lsha512 }, { "md5", Lmd5 }, { "hmac_sha1", Lhmac_sha1 }, { "hmac_sha256", Lhmac_sha256 }, { "hmac_sha512", Lhmac_sha512 }, { "hmac_md5", Lhmac_md5 }, { "scram_Hi_sha1", LscramHi }, { NULL, NULL } }; LUALIB_API int luaopen_util_hashes(lua_State *L) { luaL_register(L, "hashes", Reg); lua_pushliteral(L, "version"); /** version */ lua_pushliteral(L, "-3.14"); lua_settable(L,-3); return 1; } prosody-0.9.1/util-src/signal.c0000644000175000017500000002212212213321667016311 0ustar matthewmatthew/* * signal.c -- Signal Handler Library for Lua * * Version: 1.000+changes * * Copyright (C) 2007 Patrick J. Donnelly (batrick@batbytes.com) * * This software is distributed under the same license as Lua 5.0: * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include "lua.h" #include "lauxlib.h" #ifndef lsig #define lsig struct lua_signal { char *name; /* name of the signal */ int sig; /* the signal */ }; #endif #define LUA_SIGNAL "lua_signal" static const struct lua_signal lua_signals[] = { /* ANSI C signals */ #ifdef SIGABRT {"SIGABRT", SIGABRT}, #endif #ifdef SIGFPE {"SIGFPE", SIGFPE}, #endif #ifdef SIGILL {"SIGILL", SIGILL}, #endif #ifdef SIGINT {"SIGINT", SIGINT}, #endif #ifdef SIGSEGV {"SIGSEGV", SIGSEGV}, #endif #ifdef SIGTERM {"SIGTERM", SIGTERM}, #endif /* posix signals */ #ifdef SIGHUP {"SIGHUP", SIGHUP}, #endif #ifdef SIGQUIT {"SIGQUIT", SIGQUIT}, #endif #ifdef SIGTRAP {"SIGTRAP", SIGTRAP}, #endif #ifdef SIGKILL {"SIGKILL", SIGKILL}, #endif #ifdef SIGUSR1 {"SIGUSR1", SIGUSR1}, #endif #ifdef SIGUSR2 {"SIGUSR2", SIGUSR2}, #endif #ifdef SIGPIPE {"SIGPIPE", SIGPIPE}, #endif #ifdef SIGALRM {"SIGALRM", SIGALRM}, #endif #ifdef SIGCHLD {"SIGCHLD", SIGCHLD}, #endif #ifdef SIGCONT {"SIGCONT", SIGCONT}, #endif #ifdef SIGSTOP {"SIGSTOP", SIGSTOP}, #endif #ifdef SIGTTIN {"SIGTTIN", SIGTTIN}, #endif #ifdef SIGTTOU {"SIGTTOU", SIGTTOU}, #endif /* some BSD signals */ #ifdef SIGIOT {"SIGIOT", SIGIOT}, #endif #ifdef SIGBUS {"SIGBUS", SIGBUS}, #endif #ifdef SIGCLD {"SIGCLD", SIGCLD}, #endif #ifdef SIGURG {"SIGURG", SIGURG}, #endif #ifdef SIGXCPU {"SIGXCPU", SIGXCPU}, #endif #ifdef SIGXFSZ {"SIGXFSZ", SIGXFSZ}, #endif #ifdef SIGVTALRM {"SIGVTALRM", SIGVTALRM}, #endif #ifdef SIGPROF {"SIGPROF", SIGPROF}, #endif #ifdef SIGWINCH {"SIGWINCH", SIGWINCH}, #endif #ifdef SIGPOLL {"SIGPOLL", SIGPOLL}, #endif #ifdef SIGIO {"SIGIO", SIGIO}, #endif /* add odd signals */ #ifdef SIGSTKFLT {"SIGSTKFLT", SIGSTKFLT}, /* stack fault */ #endif #ifdef SIGSYS {"SIGSYS", SIGSYS}, #endif {NULL, 0} }; static lua_State *Lsig = NULL; static lua_Hook Hsig = NULL; static int Hmask = 0; static int Hcount = 0; static struct signal_event { int Nsig; struct signal_event *next_event; } *signal_queue = NULL; static struct signal_event *last_event = NULL; static void sighook(lua_State *L, lua_Debug *ar) { struct signal_event *event; /* restore the old hook */ lua_sethook(L, Hsig, Hmask, Hcount); lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); while((event = signal_queue)) { lua_pushnumber(L, event->Nsig); lua_gettable(L, -2); lua_call(L, 0, 0); signal_queue = event->next_event; free(event); }; lua_pop(L, 1); /* pop lua_signal table */ } static void handle(int sig) { if(!signal_queue) { /* Store the existing debug hook (if any) and its parameters */ Hsig = lua_gethook(Lsig); Hmask = lua_gethookmask(Lsig); Hcount = lua_gethookcount(Lsig); signal_queue = malloc(sizeof(struct signal_event)); signal_queue->Nsig = sig; signal_queue->next_event = NULL; last_event = signal_queue; /* Set our new debug hook */ lua_sethook(Lsig, sighook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } else { last_event->next_event = malloc(sizeof(struct signal_event)); last_event->next_event->Nsig = sig; last_event->next_event->next_event = NULL; last_event = last_event->next_event; } } /* * l_signal == signal(signal [, func [, chook]]) * * signal = signal number or string * func = Lua function to call * chook = catch within C functions * if caught, Lua function _must_ * exit, as the stack is most likely * in an unstable state. */ static int l_signal(lua_State *L) { int args = lua_gettop(L); int t, sig; /* type, signal */ /* get type of signal */ luaL_checkany(L, 1); t = lua_type(L, 1); if (t == LUA_TNUMBER) sig = (int) lua_tonumber(L, 1); else if (t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 1); lua_gettable(L, -2); if (!lua_isnumber(L, -1)) luaL_error(L, "invalid signal string"); sig = (int) lua_tonumber(L, -1); lua_pop(L, 1); /* get rid of number we pushed */ } else luaL_checknumber(L, 1); /* will always error, with good error msg */ /* set handler */ if (args == 1 || lua_isnil(L, 2)) /* clear handler */ { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushnumber(L, sig); lua_gettable(L, -2); /* return old handler */ lua_pushnumber(L, sig); lua_pushnil(L); lua_settable(L, -4); lua_remove(L, -2); /* remove LUA_SIGNAL table */ signal(sig, SIG_DFL); } else { luaL_checktype(L, 2, LUA_TFUNCTION); lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushnumber(L, sig); lua_pushvalue(L, 2); lua_settable(L, -3); /* Set the state for the handler */ Lsig = L; if (lua_toboolean(L, 3)) /* c hook? */ { if (signal(sig, handle) == SIG_ERR) lua_pushboolean(L, 0); else lua_pushboolean(L, 1); } else /* lua_hook */ { if (signal(sig, handle) == SIG_ERR) lua_pushboolean(L, 0); else lua_pushboolean(L, 1); } } return 1; } /* * l_raise == raise(signal) * * signal = signal number or string */ static int l_raise(lua_State *L) { /* int args = lua_gettop(L); */ int t = 0; /* type */ lua_Number ret; luaL_checkany(L, 1); t = lua_type(L, 1); if (t == LUA_TNUMBER) { ret = (lua_Number) raise((int) lua_tonumber(L, 1)); lua_pushnumber(L, ret); } else if (t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 1); lua_gettable(L, -2); if (!lua_isnumber(L, -1)) luaL_error(L, "invalid signal string"); ret = (lua_Number) raise((int) lua_tonumber(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ lua_pushnumber(L, ret); } else luaL_checknumber(L, 1); /* will always error, with good error msg */ return 1; } #if defined(__unix__) || defined(__APPLE__) /* define some posix only functions */ /* * l_kill == kill(pid, signal) * * pid = process id * signal = signal number or string */ static int l_kill(lua_State *L) { int t; /* type */ lua_Number ret; /* return value */ luaL_checknumber(L, 1); /* must be int for pid */ luaL_checkany(L, 2); /* check for a second arg */ t = lua_type(L, 2); if (t == LUA_TNUMBER) { ret = (lua_Number) kill((int) lua_tonumber(L, 1), (int) lua_tonumber(L, 2)); lua_pushnumber(L, ret); } else if (t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 2); lua_gettable(L, -2); if (!lua_isnumber(L, -1)) luaL_error(L, "invalid signal string"); ret = (lua_Number) kill((int) lua_tonumber(L, 1), (int) lua_tonumber(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ lua_pushnumber(L, ret); } else luaL_checknumber(L, 2); /* will always error, with good error msg */ return 1; } #endif static const struct luaL_Reg lsignal_lib[] = { {"signal", l_signal}, {"raise", l_raise}, #if defined(__unix__) || defined(__APPLE__) {"kill", l_kill}, #endif {NULL, NULL} }; int luaopen_util_signal(lua_State *L) { int i = 0; /* add the library */ luaL_register(L, "signal", lsignal_lib); /* push lua_signals table into the registry */ /* put the signals inside the library table too, * they are only a reference */ lua_pushstring(L, LUA_SIGNAL); lua_createtable(L, 0, 0); while (lua_signals[i].name != NULL) { /* registry table */ lua_pushstring(L, lua_signals[i].name); lua_pushnumber(L, lua_signals[i].sig); lua_settable(L, -3); /* signal table */ lua_pushstring(L, lua_signals[i].name); lua_pushnumber(L, lua_signals[i].sig); lua_settable(L, -5); i++; } /* add newtable to the registry */ lua_settable(L, LUA_REGISTRYINDEX); return 1; } prosody-0.9.1/util-src/Makefile0000644000175000017500000000151012213321667016326 0ustar matthewmatthew include ../config.unix LUA_SUFFIX?=5.1 LUA_INCDIR?=/usr/include/lua$(LUA_SUFFIX) LUA_LIB?=lua$(LUA_SUFFIX) IDN_LIB?=idn OPENSSL_LIB?=crypto CC?=gcc CXX?=g++ LD?=gcc CFLAGS+=-ggdb .PHONY: all install clean .SUFFIXES: .c .o .so all: encodings.so hashes.so net.so pposix.so signal.so install: encodings.so hashes.so net.so pposix.so signal.so install *.so ../util/ clean: rm -f *.o rm -f *.so rm -f ../util/*.so encodings.so: encodings.o MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; $(CC) -o $@ $< $(LDFLAGS) $(IDNA_LIBS) hashes.so: hashes.o MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; $(CC) -o $@ $< $(LDFLAGS) -l$(OPENSSL_LIB) .c.o: $(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o $@ $< .o.so: MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET; $(LD) -o $@ $< $(LDFLAGS) prosody-0.9.1/util-src/encodings.c0000644000175000017500000002272212213321667017013 0ustar matthewmatthew/* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * encodings.c * Lua library for base64, stringprep and idna encodings */ /* Newer MSVC compilers deprecate strcpy as unsafe, but we use it in a safe way */ #define _CRT_SECURE_NO_DEPRECATE #include #include #include "lua.h" #include "lauxlib.h" /***************** BASE64 *****************/ static const char code[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void base64_encode(luaL_Buffer *b, unsigned int c1, unsigned int c2, unsigned int c3, int n) { unsigned long tuple=c3+256UL*(c2+256UL*c1); int i; char s[4]; for (i=0; i<4; i++) { s[3-i] = code[tuple % 64]; tuple /= 64; } for (i=n+1; i<4; i++) s[i]='='; luaL_addlstring(b,s,4); } static int Lbase64_encode(lua_State *L) /** encode(s) */ { size_t l; const unsigned char *s=(const unsigned char*)luaL_checklstring(L,1,&l); luaL_Buffer b; int n; luaL_buffinit(L,&b); for (n=l/3; n--; s+=3) base64_encode(&b,s[0],s[1],s[2],3); switch (l%3) { case 1: base64_encode(&b,s[0],0,0,1); break; case 2: base64_encode(&b,s[0],s[1],0,2); break; } luaL_pushresult(&b); return 1; } static void base64_decode(luaL_Buffer *b, int c1, int c2, int c3, int c4, int n) { unsigned long tuple=c4+64L*(c3+64L*(c2+64L*c1)); char s[3]; switch (--n) { case 3: s[2]=(char) tuple; case 2: s[1]=(char) (tuple >> 8); case 1: s[0]=(char) (tuple >> 16); } luaL_addlstring(b,s,n); } static int Lbase64_decode(lua_State *L) /** decode(s) */ { size_t l; const char *s=luaL_checklstring(L,1,&l); luaL_Buffer b; int n=0; char t[4]; luaL_buffinit(L,&b); for (;;) { int c=*s++; switch (c) { const char *p; default: p=strchr(code,c); if (p==NULL) return 0; t[n++]= (char) (p-code); if (n==4) { base64_decode(&b,t[0],t[1],t[2],t[3],4); n=0; } break; case '=': switch (n) { case 1: base64_decode(&b,t[0],0,0,0,1); break; case 2: base64_decode(&b,t[0],t[1],0,0,2); break; case 3: base64_decode(&b,t[0],t[1],t[2],0,3); break; } n=0; break; case 0: luaL_pushresult(&b); return 1; case '\n': case '\r': case '\t': case ' ': case '\f': case '\b': break; } } } static const luaL_Reg Reg_base64[] = { { "encode", Lbase64_encode }, { "decode", Lbase64_decode }, { NULL, NULL } }; /***************** STRINGPREP *****************/ #ifdef USE_STRINGPREP_ICU #include #include #include static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) { size_t input_len; int32_t unprepped_len, prepped_len, output_len; const char *input; char output[1024]; UChar unprepped[1024]; /* Temporary unicode buffer (1024 characters) */ UChar prepped[1024]; UErrorCode err = U_ZERO_ERROR; if(!lua_isstring(L, 1)) { lua_pushnil(L); return 1; } input = lua_tolstring(L, 1, &input_len); if (input_len >= 1024) { lua_pushnil(L); return 1; } u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err); if (U_SUCCESS(err) && output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); return 1; } } UStringPrepProfile *icu_nameprep; UStringPrepProfile *icu_nodeprep; UStringPrepProfile *icu_resourceprep; UStringPrepProfile *icu_saslprep; /* initialize global ICU stringprep profiles */ void init_icu() { UErrorCode err = U_ZERO_ERROR; utrace_setLevel(UTRACE_VERBOSE); icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err); icu_nodeprep = usprep_openByType(USPREP_RFC3920_NODEPREP, &err); icu_resourceprep = usprep_openByType(USPREP_RFC3920_RESOURCEPREP, &err); icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err); if (U_FAILURE(err)) fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err)); } #define MAKE_PREP_FUNC(myFunc, prep) \ static int myFunc(lua_State *L) { return icu_stringprep_prep(L, prep); } MAKE_PREP_FUNC(Lstringprep_nameprep, icu_nameprep) /** stringprep.nameprep(s) */ MAKE_PREP_FUNC(Lstringprep_nodeprep, icu_nodeprep) /** stringprep.nodeprep(s) */ MAKE_PREP_FUNC(Lstringprep_resourceprep, icu_resourceprep) /** stringprep.resourceprep(s) */ MAKE_PREP_FUNC(Lstringprep_saslprep, icu_saslprep) /** stringprep.saslprep(s) */ static const luaL_Reg Reg_stringprep[] = { { "nameprep", Lstringprep_nameprep }, { "nodeprep", Lstringprep_nodeprep }, { "resourceprep", Lstringprep_resourceprep }, { "saslprep", Lstringprep_saslprep }, { NULL, NULL } }; #else /* USE_STRINGPREP_ICU */ /****************** libidn ********************/ #include static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { size_t len; const char *s; char string[1024]; int ret; if(!lua_isstring(L, 1)) { lua_pushnil(L); return 1; } s = lua_tolstring(L, 1, &len); if (len >= 1024) { lua_pushnil(L); return 1; /* TODO return error message */ } strcpy(string, s); ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile); if (ret == STRINGPREP_OK) { lua_pushstring(L, string); return 1; } else { lua_pushnil(L); return 1; /* TODO return error message */ } } #define MAKE_PREP_FUNC(myFunc, prep) \ static int myFunc(lua_State *L) { return stringprep_prep(L, prep); } MAKE_PREP_FUNC(Lstringprep_nameprep, stringprep_nameprep) /** stringprep.nameprep(s) */ MAKE_PREP_FUNC(Lstringprep_nodeprep, stringprep_xmpp_nodeprep) /** stringprep.nodeprep(s) */ MAKE_PREP_FUNC(Lstringprep_resourceprep, stringprep_xmpp_resourceprep) /** stringprep.resourceprep(s) */ MAKE_PREP_FUNC(Lstringprep_saslprep, stringprep_saslprep) /** stringprep.saslprep(s) */ static const luaL_Reg Reg_stringprep[] = { { "nameprep", Lstringprep_nameprep }, { "nodeprep", Lstringprep_nodeprep }, { "resourceprep", Lstringprep_resourceprep }, { "saslprep", Lstringprep_saslprep }, { NULL, NULL } }; #endif /***************** IDNA *****************/ #ifdef USE_STRINGPREP_ICU #include #include /* IDNA2003 or IDNA2008 ? ? ? */ static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */ { size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); UChar ustr[1024]; UErrorCode err = U_ZERO_ERROR; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); if (U_SUCCESS(err) && output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); return 1; } } static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */ { size_t len; int32_t ulen, dest_len, output_len; const char *s = luaL_checklstring(L, 1, &len); UChar ustr[1024]; UErrorCode err = U_ZERO_ERROR; UChar dest[1024]; char output[1024]; u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); if (U_FAILURE(err)) { lua_pushnil(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); if (U_SUCCESS(err) && output_len < 1024) lua_pushlstring(L, output, output_len); else lua_pushnil(L); return 1; } } #else /* USE_STRINGPREP_ICU */ /****************** libidn ********************/ #include #include static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */ { size_t len; const char *s = luaL_checklstring(L, 1, &len); char* output = NULL; int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES); if (ret == IDNA_SUCCESS) { lua_pushstring(L, output); idn_free(output); return 1; } else { lua_pushnil(L); idn_free(output); return 1; /* TODO return error message */ } } static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */ { size_t len; const char *s = luaL_checklstring(L, 1, &len); char* output = NULL; int ret = idna_to_unicode_8z8z(s, &output, 0); if (ret == IDNA_SUCCESS) { lua_pushstring(L, output); idn_free(output); return 1; } else { lua_pushnil(L); idn_free(output); return 1; /* TODO return error message */ } } #endif static const luaL_Reg Reg_idna[] = { { "to_ascii", Lidna_to_ascii }, { "to_unicode", Lidna_to_unicode }, { NULL, NULL } }; /***************** end *****************/ static const luaL_Reg Reg[] = { { NULL, NULL } }; LUALIB_API int luaopen_util_encodings(lua_State *L) { #ifdef USE_STRINGPREP_ICU init_icu(); #endif luaL_register(L, "encodings", Reg); lua_pushliteral(L, "base64"); lua_newtable(L); luaL_register(L, NULL, Reg_base64); lua_settable(L,-3); lua_pushliteral(L, "stringprep"); lua_newtable(L); luaL_register(L, NULL, Reg_stringprep); lua_settable(L,-3); lua_pushliteral(L, "idna"); lua_newtable(L); luaL_register(L, NULL, Reg_idna); lua_settable(L,-3); lua_pushliteral(L, "version"); /** version */ lua_pushliteral(L, "-3.14"); lua_settable(L,-3); return 1; } prosody-0.9.1/util-src/Makefile.win0000644000175000017500000000250112213321667017123 0ustar matthewmatthew LUA_PATH=$(LUA_DEV) IDN_PATH=..\..\libidn-1.15 OPENSSL_PATH=..\..\openssl-0.9.8k LUA_INCLUDE=$(LUA_PATH)\include LUA_LIB=$(LUA_PATH)\lib\lua5.1.lib IDN_LIB=$(IDN_PATH)\win32\lib\libidn.lib IDN_INCLUDE1=$(IDN_PATH)\lib IDN_INCLUDE2=$(IDN_PATH)\win32\include OPENSSL_LIB=$(OPENSSL_PATH)\out32dll\libeay32.lib OPENSSL_INCLUDE=$(OPENSSL_PATH)\include CL=cl /LD /MD /nologo all: encodings.dll hashes.dll windows.dll install: encodings.dll hashes.dll windows.dll copy /Y *.dll ..\util\ clean: del encodings.dll encodings.exp encodings.lib encodings.obj encodings.dll.manifest del hashes.dll hashes.exp hashes.lib hashes.obj hashes.dll.manifest del windows.dll windows.exp windows.lib windows.obj windows.dll.manifest encodings.dll: encodings.c $(CL) encodings.c /I"$(LUA_INCLUDE)" /I"$(IDN_INCLUDE1)" /I"$(IDN_INCLUDE2)" /link "$(LUA_LIB)" "$(IDN_LIB)" /export:luaopen_util_encodings del encodings.exp encodings.lib encodings.obj encodings.dll.manifest hashes.dll: hashes.c $(CL) hashes.c /I"$(LUA_INCLUDE)" /I"$(OPENSSL_INCLUDE)" /link "$(LUA_LIB)" "$(OPENSSL_LIB)" /export:luaopen_util_hashes del hashes.exp hashes.lib hashes.obj hashes.dll.manifest windows.dll: windows.c $(CL) windows.c /I"$(LUA_INCLUDE)" /link "$(LUA_LIB)" dnsapi.lib /export:luaopen_util_windows del windows.exp windows.lib windows.obj windows.dll.manifest prosody-0.9.1/util-src/pposix.c0000644000175000017500000003562512213321667016372 0ustar matthewmatthew/* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- Copyright (C) 2009 Tobias Markmann -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * pposix.c * POSIX support functions for Lua */ #define MODULE_VERSION "0.3.6" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua.h" #include "lualib.h" #include "lauxlib.h" #include #if defined(__linux__) && defined(_GNU_SOURCE) #include #endif #if (defined(_SVID_SOURCE) && !defined(WITHOUT_MALLINFO)) #include #define WITH_MALLINFO #endif /* Daemonization support */ static int lc_daemonize(lua_State *L) { pid_t pid; if ( getppid() == 1 ) { lua_pushboolean(L, 0); lua_pushstring(L, "already-daemonized"); return 2; } /* Attempt initial fork */ if((pid = fork()) < 0) { /* Forking failed */ lua_pushboolean(L, 0); lua_pushstring(L, "fork-failed"); return 2; } else if(pid != 0) { /* We are the parent process */ lua_pushboolean(L, 1); lua_pushnumber(L, pid); return 2; } /* and we are the child process */ if(setsid() == -1) { /* We failed to become session leader */ /* (we probably already were) */ lua_pushboolean(L, 0); lua_pushstring(L, "setsid-failed"); return 2; } /* Close stdin, stdout, stderr */ close(0); close(1); close(2); /* Make sure accidental use of FDs 0, 1, 2 don't cause weirdness */ open("/dev/null", O_RDONLY); open("/dev/null", O_WRONLY); open("/dev/null", O_WRONLY); /* Final fork, use it wisely */ if(fork()) exit(0); /* Show's over, let's continue */ lua_pushboolean(L, 1); lua_pushnil(L); return 2; } /* Syslog support */ const char * const facility_strings[] = { "auth", #if !(defined(sun) || defined(__sun)) "authpriv", #endif "cron", "daemon", #if !(defined(sun) || defined(__sun)) "ftp", #endif "kern", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7", "lpr", "mail", "syslog", "user", "uucp", NULL }; int facility_constants[] = { LOG_AUTH, #if !(defined(sun) || defined(__sun)) LOG_AUTHPRIV, #endif LOG_CRON, LOG_DAEMON, #if !(defined(sun) || defined(__sun)) LOG_FTP, #endif LOG_KERN, LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_SYSLOG, LOG_USER, LOG_UUCP, -1 }; /* " The parameter ident in the call of openlog() is probably stored as-is. Thus, if the string it points to is changed, syslog() may start prepending the changed string, and if the string it points to ceases to exist, the results are undefined. Most portable is to use a string constant. " -- syslog manpage */ char* syslog_ident = NULL; int lc_syslog_open(lua_State* L) { int facility = luaL_checkoption(L, 2, "daemon", facility_strings); facility = facility_constants[facility]; luaL_checkstring(L, 1); if(syslog_ident) free(syslog_ident); syslog_ident = strdup(lua_tostring(L, 1)); openlog(syslog_ident, LOG_PID, facility); return 0; } const char * const level_strings[] = { "debug", "info", "notice", "warn", "error", NULL }; int level_constants[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING, LOG_CRIT, -1 }; int lc_syslog_log(lua_State* L) { int level = level_constants[luaL_checkoption(L, 1, "notice", level_strings)]; 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)); return 0; } int lc_syslog_close(lua_State* L) { closelog(); if(syslog_ident) { free(syslog_ident); syslog_ident = NULL; } return 0; } int lc_syslog_setmask(lua_State* L) { int level_idx = luaL_checkoption(L, 1, "notice", level_strings); int mask = 0; do { mask |= LOG_MASK(level_constants[level_idx]); } while (++level_idx<=4); setlogmask(mask); return 0; } /* getpid */ int lc_getpid(lua_State* L) { lua_pushinteger(L, getpid()); return 1; } /* UID/GID functions */ int lc_getuid(lua_State* L) { lua_pushinteger(L, getuid()); return 1; } int lc_getgid(lua_State* L) { lua_pushinteger(L, getgid()); return 1; } int lc_setuid(lua_State* L) { int uid = -1; if(lua_gettop(L) < 1) return 0; if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) { /* Passed UID is actually a string, so look up the UID */ struct passwd *p; p = getpwnam(lua_tostring(L, 1)); if(!p) { lua_pushboolean(L, 0); lua_pushstring(L, "no-such-user"); return 2; } uid = p->pw_uid; } else { uid = lua_tonumber(L, 1); } if(uid>-1) { /* Ok, attempt setuid */ errno = 0; if(setuid(uid)) { /* Fail */ lua_pushboolean(L, 0); switch(errno) { case EINVAL: lua_pushstring(L, "invalid-uid"); break; case EPERM: lua_pushstring(L, "permission-denied"); break; default: lua_pushstring(L, "unknown-error"); } return 2; } else { /* Success! */ lua_pushboolean(L, 1); return 1; } } /* Seems we couldn't find a valid UID to switch to */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-uid"); return 2; } int lc_setgid(lua_State* L) { int gid = -1; if(lua_gettop(L) < 1) return 0; if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) { /* Passed GID is actually a string, so look up the GID */ struct group *g; g = getgrnam(lua_tostring(L, 1)); if(!g) { lua_pushboolean(L, 0); lua_pushstring(L, "no-such-group"); return 2; } gid = g->gr_gid; } else { gid = lua_tonumber(L, 1); } if(gid>-1) { /* Ok, attempt setgid */ errno = 0; if(setgid(gid)) { /* Fail */ lua_pushboolean(L, 0); switch(errno) { case EINVAL: lua_pushstring(L, "invalid-gid"); break; case EPERM: lua_pushstring(L, "permission-denied"); break; default: lua_pushstring(L, "unknown-error"); } return 2; } else { /* Success! */ lua_pushboolean(L, 1); return 1; } } /* Seems we couldn't find a valid GID to switch to */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-gid"); return 2; } int lc_initgroups(lua_State* L) { int ret; gid_t gid; struct passwd *p; if(!lua_isstring(L, 1)) { lua_pushnil(L); lua_pushstring(L, "invalid-username"); return 2; } p = getpwnam(lua_tostring(L, 1)); if(!p) { lua_pushnil(L); lua_pushstring(L, "no-such-user"); return 2; } if(lua_gettop(L) < 2) lua_pushnil(L); switch(lua_type(L, 2)) { case LUA_TNIL: gid = p->pw_gid; break; case LUA_TNUMBER: gid = lua_tointeger(L, 2); break; default: lua_pushnil(L); lua_pushstring(L, "invalid-gid"); return 2; } ret = initgroups(lua_tostring(L, 1), gid); if(ret) { switch(errno) { case ENOMEM: lua_pushnil(L); lua_pushstring(L, "no-memory"); break; case EPERM: lua_pushnil(L); lua_pushstring(L, "permission-denied"); break; default: lua_pushnil(L); lua_pushstring(L, "unknown-error"); } } else { lua_pushboolean(L, 1); lua_pushnil(L); } return 2; } int lc_umask(lua_State* L) { char old_mode_string[7]; mode_t old_mode = umask(strtoul(luaL_checkstring(L, 1), NULL, 8)); snprintf(old_mode_string, sizeof(old_mode_string), "%03o", old_mode); old_mode_string[sizeof(old_mode_string)-1] = 0; lua_pushstring(L, old_mode_string); return 1; } int lc_mkdir(lua_State* L) { int ret = mkdir(luaL_checkstring(L, 1), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); /* mode 775 */ lua_pushboolean(L, ret==0); if(ret) { lua_pushstring(L, strerror(errno)); return 2; } return 1; } /* Like POSIX's setrlimit()/getrlimit() API functions. * * Syntax: * pposix.setrlimit( resource, soft limit, hard limit) * * Any negative limit will be replace with the current limit by an additional call of getrlimit(). * * Example usage: * pposix.setrlimit("NOFILE", 1000, 2000) */ int string2resource(const char *s) { if (!strcmp(s, "CORE")) return RLIMIT_CORE; if (!strcmp(s, "CPU")) return RLIMIT_CPU; if (!strcmp(s, "DATA")) return RLIMIT_DATA; if (!strcmp(s, "FSIZE")) return RLIMIT_FSIZE; if (!strcmp(s, "NOFILE")) return RLIMIT_NOFILE; if (!strcmp(s, "STACK")) return RLIMIT_STACK; #if !(defined(sun) || defined(__sun)) if (!strcmp(s, "MEMLOCK")) return RLIMIT_MEMLOCK; if (!strcmp(s, "NPROC")) return RLIMIT_NPROC; if (!strcmp(s, "RSS")) return RLIMIT_RSS; #endif #ifdef RLIMIT_NICE if (!strcmp(s, "NICE")) return RLIMIT_NICE; #endif return -1; } int lc_setrlimit(lua_State *L) { int arguments = lua_gettop(L); int softlimit = -1; int hardlimit = -1; const char *resource = NULL; int rid = -1; if(arguments < 1 || arguments > 3) { lua_pushboolean(L, 0); lua_pushstring(L, "incorrect-arguments"); return 2; } resource = luaL_checkstring(L, 1); softlimit = luaL_checkinteger(L, 2); hardlimit = luaL_checkinteger(L, 3); rid = string2resource(resource); if (rid != -1) { struct rlimit lim; struct rlimit lim_current; if (softlimit < 0 || hardlimit < 0) { if (getrlimit(rid, &lim_current)) { lua_pushboolean(L, 0); lua_pushstring(L, "getrlimit-failed"); return 2; } } if (softlimit < 0) lim.rlim_cur = lim_current.rlim_cur; else lim.rlim_cur = softlimit; if (hardlimit < 0) lim.rlim_max = lim_current.rlim_max; else lim.rlim_max = hardlimit; if (setrlimit(rid, &lim)) { lua_pushboolean(L, 0); lua_pushstring(L, "setrlimit-failed"); return 2; } } else { /* Unsupported resoucrce. Sorry I'm pretty limited by POSIX standard. */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-resource"); return 2; } lua_pushboolean(L, 1); return 1; } int lc_getrlimit(lua_State *L) { int arguments = lua_gettop(L); const char *resource = NULL; int rid = -1; struct rlimit lim; if (arguments != 1) { lua_pushboolean(L, 0); lua_pushstring(L, "invalid-arguments"); return 2; } resource = luaL_checkstring(L, 1); rid = string2resource(resource); if (rid != -1) { if (getrlimit(rid, &lim)) { lua_pushboolean(L, 0); lua_pushstring(L, "getrlimit-failed."); return 2; } } else { /* Unsupported resoucrce. Sorry I'm pretty limited by POSIX standard. */ lua_pushboolean(L, 0); lua_pushstring(L, "invalid-resource"); return 2; } lua_pushboolean(L, 1); lua_pushnumber(L, lim.rlim_cur); lua_pushnumber(L, lim.rlim_max); return 3; } int lc_abort(lua_State* L) { abort(); return 0; } int lc_uname(lua_State* L) { struct utsname uname_info; if(uname(&uname_info) != 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_newtable(L); lua_pushstring(L, uname_info.sysname); lua_setfield(L, -2, "sysname"); lua_pushstring(L, uname_info.nodename); lua_setfield(L, -2, "nodename"); lua_pushstring(L, uname_info.release); lua_setfield(L, -2, "release"); lua_pushstring(L, uname_info.version); lua_setfield(L, -2, "version"); lua_pushstring(L, uname_info.machine); lua_setfield(L, -2, "machine"); return 1; } int lc_setenv(lua_State* L) { const char *var = luaL_checkstring(L, 1); const char *value; /* If the second argument is nil or nothing, unset the var */ if(lua_isnoneornil(L, 2)) { if(unsetenv(var) != 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } value = luaL_checkstring(L, 2); if(setenv(var, value, 1) != 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } #ifdef WITH_MALLINFO int lc_meminfo(lua_State* L) { struct mallinfo info = mallinfo(); lua_newtable(L); /* This is the total size of memory allocated with sbrk by malloc, in bytes. */ lua_pushinteger(L, info.arena); lua_setfield(L, -2, "allocated"); /* This is the total size of memory allocated with mmap, in bytes. */ lua_pushinteger(L, info.hblkhd); lua_setfield(L, -2, "allocated_mmap"); /* This is the total size of memory occupied by chunks handed out by malloc. */ lua_pushinteger(L, info.uordblks); lua_setfield(L, -2, "used"); /* This is the total size of memory occupied by free (not in use) chunks. */ lua_pushinteger(L, info.fordblks); lua_setfield(L, -2, "unused"); /* This is the size of the top-most releasable chunk that normally borders the end of the heap (i.e., the high end of the virtual address space's data segment). */ lua_pushinteger(L, info.keepcost); lua_setfield(L, -2, "returnable"); return 1; } #endif /* File handle extraction blatantly stolen from * https://github.com/rrthomas/luaposix/blob/master/lposix.c#L631 * */ #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L || defined(_GNU_SOURCE) int lc_fallocate(lua_State* L) { off_t offset, len; FILE *f = *(FILE**) luaL_checkudata(L, 1, LUA_FILEHANDLE); offset = luaL_checkinteger(L, 2); len = luaL_checkinteger(L, 3); #if defined(__linux__) && defined(_GNU_SOURCE) if(fallocate(fileno(f), FALLOC_FL_KEEP_SIZE, offset, len) == 0) { lua_pushboolean(L, 1); return 1; } if(errno != ENOSYS && errno != EOPNOTSUPP) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } #else #warning Only using posix_fallocate() fallback. #warning Linux fallocate() is strongly recommended if available: recompile with -D_GNU_SOURCE #warning Note that posix_fallocate() will still be used on filesystems that dont support fallocate() #endif if(posix_fallocate(fileno(f), offset, len) == 0) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); lua_pushstring(L, strerror(errno)); /* 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); return 2; } } #endif /* Register functions */ int luaopen_util_pposix(lua_State *L) { luaL_Reg exports[] = { { "abort", lc_abort }, { "daemonize", lc_daemonize }, { "syslog_open", lc_syslog_open }, { "syslog_close", lc_syslog_close }, { "syslog_log", lc_syslog_log }, { "syslog_setminlevel", lc_syslog_setmask }, { "getpid", lc_getpid }, { "getuid", lc_getuid }, { "getgid", lc_getgid }, { "setuid", lc_setuid }, { "setgid", lc_setgid }, { "initgroups", lc_initgroups }, { "umask", lc_umask }, { "mkdir", lc_mkdir }, { "setrlimit", lc_setrlimit }, { "getrlimit", lc_getrlimit }, { "uname", lc_uname }, { "setenv", lc_setenv }, #ifdef WITH_MALLINFO { "meminfo", lc_meminfo }, #endif #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L || defined(_GNU_SOURCE) { "fallocate", lc_fallocate }, #endif { NULL, NULL } }; luaL_register(L, "pposix", exports); lua_pushliteral(L, "pposix"); lua_setfield(L, -2, "_NAME"); lua_pushliteral(L, MODULE_VERSION); lua_setfield(L, -2, "_VERSION"); return 1; } prosody-0.9.1/util-src/windows.c0000644000175000017500000000467212213321667016540 0ustar matthewmatthew/* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- */ /* * windows.c * Windows support functions for Lua */ #include #include #include #include "lua.h" #include "lauxlib.h" static int Lget_nameservers(lua_State *L) { char stack_buffer[1024]; // stack allocated buffer IP4_ARRAY* ips = (IP4_ARRAY*) stack_buffer; DWORD len = sizeof(stack_buffer); DNS_STATUS status; status = DnsQueryConfig(DnsConfigDnsServerList, FALSE, NULL, NULL, ips, &len); if (status == 0) { DWORD i; lua_createtable(L, ips->AddrCount, 0); for (i = 0; i < ips->AddrCount; i++) { DWORD ip = ips->AddrArray[i]; char ip_str[16] = ""; sprintf_s(ip_str, sizeof(ip_str), "%d.%d.%d.%d", (ip >> 0) & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255); lua_pushstring(L, ip_str); lua_rawseti(L, -2, i+1); } return 1; } else { lua_pushnil(L); lua_pushfstring(L, "DnsQueryConfig returned %d", status); return 2; } } static int lerror(lua_State *L, char* string) { lua_pushnil(L); lua_pushfstring(L, "%s: %d", string, GetLastError()); return 2; } static int Lget_consolecolor(lua_State *L) { HWND console = GetStdHandle(STD_OUTPUT_HANDLE); WORD color; DWORD read_len; CONSOLE_SCREEN_BUFFER_INFO info; if (console == INVALID_HANDLE_VALUE) return lerror(L, "GetStdHandle"); if (!GetConsoleScreenBufferInfo(console, &info)) return lerror(L, "GetConsoleScreenBufferInfo"); if (!ReadConsoleOutputAttribute(console, &color, sizeof(WORD), info.dwCursorPosition, &read_len)) return lerror(L, "ReadConsoleOutputAttribute"); lua_pushnumber(L, color); return 1; } static int Lset_consolecolor(lua_State *L) { int color = luaL_checkint(L, 1); HWND console = GetStdHandle(STD_OUTPUT_HANDLE); if (console == INVALID_HANDLE_VALUE) return lerror(L, "GetStdHandle"); if (!SetConsoleTextAttribute(console, color)) return lerror(L, "SetConsoleTextAttribute"); lua_pushboolean(L, 1); return 1; } static const luaL_Reg Reg[] = { { "get_nameservers", Lget_nameservers }, { "get_consolecolor", Lget_consolecolor }, { "set_consolecolor", Lset_consolecolor }, { NULL, NULL } }; LUALIB_API int luaopen_util_windows(lua_State *L) { luaL_register(L, "windows", Reg); lua_pushliteral(L, "version"); /** version */ lua_pushliteral(L, "-3.14"); lua_settable(L,-3); return 1; } prosody-0.9.1/Makefile0000644000175000017500000000456112213321667014575 0ustar matthewmatthew include config.unix BIN = $(DESTDIR)$(PREFIX)/bin CONFIG = $(DESTDIR)$(SYSCONFDIR) MODULES = $(DESTDIR)$(PREFIX)/lib/prosody/modules SOURCE = $(DESTDIR)$(PREFIX)/lib/prosody DATA = $(DESTDIR)$(DATADIR) MAN = $(DESTDIR)$(PREFIX)/share/man INSTALLEDSOURCE = $(PREFIX)/lib/prosody INSTALLEDCONFIG = $(SYSCONFDIR) INSTALLEDMODULES = $(PREFIX)/lib/prosody/modules INSTALLEDDATA = $(DATADIR) .PHONY: all clean install all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version $(MAKE) -C util-src install install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so install -d $(BIN) $(CONFIG) $(MODULES) $(SOURCE) install -m750 -d $(DATA) install -d $(MAN)/man1 install -d $(CONFIG)/certs install -d $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util install -m755 ./prosody.install $(BIN)/prosody install -m755 ./prosodyctl.install $(BIN)/prosodyctl install -m644 core/* $(SOURCE)/core install -m644 net/*.lua $(SOURCE)/net install -d $(SOURCE)/net/http install -m644 net/http/*.lua $(SOURCE)/net/http install -m644 util/*.lua $(SOURCE)/util install -m644 util/*.so $(SOURCE)/util install -d $(SOURCE)/util/sasl install -m644 util/sasl/* $(SOURCE)/util/sasl umask 0022 && cp -r plugins/* $(MODULES) 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 -m644 prosody.version $(SOURCE)/prosody.version || true $(MAKE) install -C util-src clean: rm -f prosody.install rm -f prosodyctl.install rm -f prosody.cfg.lua.install rm -f prosody.version $(MAKE) clean -C util-src util/%.so: $(MAKE) install -C util-src %.install: % sed "1s/\blua\b/$(RUNWITH)/; \ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \ s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \ s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \ s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" < $^ > $@ prosody.cfg.lua.install: prosody.cfg.lua.dist sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' $^ > $@ prosody.version: $(wildcard prosody.release .hg/dirstate) test -e .hg/dirstate && \ hexdump -n6 -e'6/1 "%02x"' .hg/dirstate > $@ || true test -f prosody.release && \ cp prosody.release $@ || true prosody-0.9.1/INSTALL0000644000175000017500000000450412213321667014163 0ustar matthewmatthew(This file was created from http://prosody.im/doc/installing_from_source on 2013-03-31) ====== Installing from source ====== ==== Dependencies ==== There are a couple of libraries which Prosody needs installed before you can build it. These are: * lua5.1: The Lua 5.1 interpreter * liblua5.1: Lua 5.1 library * libssl 0.9.8: OpenSSL * libidn11: GNU libidn library, version 1.1 These can be installed on Debian/Ubuntu with the packages: lua5.1 liblua5.1-dev libidn11-dev libssl-dev On Mandriva try: urpmi lua liblua-devel libidn-devel libopenssl-devel On other systems... good luck, but please let me know of the best way of getting the dependencies for your system and I can add it here. ==== configure ==== The first step of building is to run the configure script. This creates a file called 'config.unix' which is used by the next step to control aspects of the build process. All options to configure can be seen by running ./configure --help. Sometimes you won't need to pass any parameters to configure, but on most systems you shall. To make this a little easier, there are a few presets which configure accepts. You can load a preset using: ./configure --ostype=PRESET Where PRESET can currently be one of: 'debian', 'macosx' or (in 0.8 and later) 'freebsd' ==== make ==== Once you have run configure successfully, then you can simply run: make Simple? :-) If you do happen to have problems at this stage, it is most likely due to the build process not finding the dependencies. Ensure you have them installed, and in the standard library paths for your system. For more help, just ask ;-) ==== install ==== At this stage you should be able to run Prosody simply with: ./prosody There is no problem with this, it is actually the easiest way to do development, as it doesn't spread parts around your system, and you can keep multiple versions around in their own directories without conflict. Should you wish to install it system-wide however, simply run: sudo make install ...it will install into /usr/local/ by default. To change this you can pass to the initial ./configure using the 'prefix' option, or edit config.unix directly. If the new path doesn't require root permission to write to, you also won't need (or want) to use 'sudo' in front of the 'make install'. Have fun, and see you on Jabber! prosody-0.9.1/tests/0000775000175000017500000000000012213321667014273 5ustar matthewmatthewprosody-0.9.1/tests/run_tests.bat0000644000175000017500000000021312213321667017003 0ustar matthewmatthew@echo off set oldpath=%path% set path=%path%;..;..\lualibs del reports\*.report lua test.lua %* set path=%oldpath% set oldpath=prosody-0.9.1/tests/test_util_sasl_scram.lua0000644000175000017500000000235312213321667021222 0ustar matthewmatthew local hmac_sha1 = require "util.hashes".hmac_sha1; local function toHex(s) return s and (s:gsub(".", function (c) return ("%02x"):format(c:byte()); end)); end function Hi(Hi) assert( toHex(Hi(hmac_sha1, "password", "salt", 1)) == "0c60c80f961f0e71f3a9b524af6012062fe037a6", [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 1)) == "0c60c80f961f0e71f3a9b524af6012062fe037a6"]]) assert( toHex(Hi(hmac_sha1, "password", "salt", 2)) == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 2)) == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"]]) assert( toHex(Hi(hmac_sha1, "password", "salt", 64)) == "a7bc9b6efea2cbd717da72d83bfcc4e17d0b6280", [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 64)) == "a7bc9b6efea2cbd717da72d83bfcc4e17d0b6280"]]) assert( toHex(Hi(hmac_sha1, "password", "salt", 4096)) == "4b007901b765489abead49d926f721d065a429c1", [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 4096)) == "4b007901b765489abead49d926f721d065a429c1"]]) -- assert( toHex(Hi(hmac_sha1, "password", "salt", 16777216)) == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", -- [[FAIL: toHex(Hi(hmac_sha1, "password", "salt", 16777216)) == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"]]) end function init(init) -- no tests end prosody-0.9.1/tests/test_net_http.lua0000644000175000017500000000327612213321667017670 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function urlencode(urlencode) assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped"); assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped"); assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped"); end function urldecode(urldecode) assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped"); assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped"); assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped"); assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped"); end function formencode(formencode) assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded"); assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded"); end function formdecode(formdecode) local t = formdecode("one=1&two=2"); assert_table(t[1]); assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1"); assert_table(t[2]); assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2"); local t = formdecode("one+two=1&two+one%26=2"); assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1"); assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2"); end prosody-0.9.1/tests/test.lua0000644000175000017500000001611312213321667015755 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function run_all_tests() package.loaded["net.connlisteners"] = { get = function () return {} end }; dotest "util.jid" dotest "util.multitable" dotest "util.rfc3484" dotest "net.http" dotest "core.modulemanager" dotest "core.stanza_router" dotest "core.s2smanager" dotest "core.configmanager" dotest "util.stanza" dotest "util.sasl.scram" dosingletest("test_sasl.lua", "latin1toutf8"); end local verbosity = tonumber(arg[1]) or 2; if os.getenv("WINDIR") then package.path = package.path..";..\\?.lua"; package.cpath = package.cpath..";..\\?.dll"; else package.path = package.path..";../?.lua"; package.cpath = package.cpath..";../?.so"; end local _realG = _G; require "util.import" local env_mt = { __index = function (t,k) return rawget(_realG, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end }; function testlib_new_env(t) return setmetatable(t or {}, env_mt); end function assert_equal(a, b, message, level) if not (a == b) then error("\n assert_equal failed: "..tostring(a).." ~= "..tostring(b)..(message and ("\n Message: "..message) or ""), (level or 1) + 1); elseif verbosity >= 4 then print("assert_equal succeeded: "..tostring(a).." == "..tostring(b)); end end function assert_table(a, message, level) assert_equal(type(a), "table", message, (level or 1) + 1); end function assert_function(a, message, level) assert_equal(type(a), "function", message, (level or 1) + 1); end function assert_string(a, message, level) assert_equal(type(a), "string", message, (level or 1) + 1); end function assert_boolean(a, message) assert_equal(type(a), "boolean", message); end function assert_is(a, message) assert_equal(not not a, true, message); end function assert_is_not(a, message) assert_equal(not not a, false, message); end function dosingletest(testname, fname) local tests = setmetatable({}, { __index = _realG }); tests.__unit = testname; tests.__test = fname; local chunk, err = loadfile(testname); if not chunk then print("WARNING: ", "Failed to load tests for "..testname, err); return; end setfenv(chunk, tests); local success, err = pcall(chunk); if not success then print("WARNING: ", "Failed to initialise tests for "..testname, err); return; end if type(tests[fname]) ~= "function" then error(testname.." has no test '"..fname.."'", 0); end local line_hook, line_info = new_line_coverage_monitor(testname); debug.sethook(line_hook, "l") local success, ret = pcall(tests[fname]); debug.sethook(); if not success then print("TEST FAILED! Unit: ["..testname.."] Function: ["..fname.."]"); print(" Location: "..ret:gsub(":%s*\n", "\n")); line_info(fname, false, report_file); elseif verbosity >= 2 then print("TEST SUCCEEDED: ", testname, fname); print(string.format("TEST COVERED %d/%d lines", line_info(fname, true, report_file))); else line_info(name, success, report_file); end end function dotest(unitname) local _fakeG = setmetatable({}, {__index = _realG}); _fakeG._G = _fakeG; local tests = setmetatable({}, { __index = _fakeG }); tests.__unit = unitname; local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua"); if not chunk then print("WARNING: ", "Failed to load tests for "..unitname, err); return; end setfenv(chunk, tests); local success, err = pcall(chunk); if not success then print("WARNING: ", "Failed to initialise tests for "..unitname, err); return; end if tests.env then setmetatable(tests.env, { __index = _realG }); end local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) }); local fn = "../"..unitname:gsub("%.", "/")..".lua"; local chunk, err = loadfile(fn); if not chunk then print("WARNING: ", "Failed to load module: "..unitname, err); return; end local oldmodule, old_M = _fakeG.module, _fakeG._M; _fakeG.module = function () _M = _G end setfenv(chunk, unit); local success, err = pcall(chunk); _fakeG.module, _fakeG._M = oldmodule, old_M; if not success then print("WARNING: ", "Failed to initialise module: "..unitname, err); return; end for name, f in pairs(unit) do local test = rawget(tests, name); if type(f) ~= "function" then if verbosity >= 3 then print("INFO: ", "Skipping "..unitname.."."..name.." because it is not a function"); end elseif type(test) ~= "function" then if verbosity >= 1 then print("WARNING: ", unitname.."."..name.." has no test!"); end else if verbosity >= 4 then print("INFO: ", "Testing "..unitname.."."..name); end local line_hook, line_info = new_line_coverage_monitor(fn); debug.sethook(line_hook, "l") local success, ret = pcall(test, f, unit); debug.sethook(); if not success then print("TEST FAILED! Unit: ["..unitname.."] Function: ["..name.."]"); print(" Location: "..ret:gsub(":%s*\n", "\n")); line_info(name, false, report_file); elseif verbosity >= 2 then print("TEST SUCCEEDED: ", unitname, name); print(string.format("TEST COVERED %d/%d lines", line_info(name, true, report_file))); else line_info(name, success, report_file); end end end end function runtest(f, msg) if not f then print("SUBTEST NOT FOUND: "..(msg or "(no description)")); return; end local success, ret = pcall(f); if success and verbosity >= 2 then print("SUBTEST PASSED: "..(msg or "(no description)")); elseif (not success) and verbosity >= 0 then print("SUBTEST FAILED: "..(msg or "(no description)")); error(ret, 0); end end function new_line_coverage_monitor(file) local lines_hit, funcs_hit = {}, {}; local total_lines, covered_lines = 0, 0; for line in io.lines(file) do total_lines = total_lines + 1; end return function (event, line) -- Line hook if not lines_hit[line] then local info = debug.getinfo(2, "fSL") if not info.source:find(file) then return; end if not funcs_hit[info.func] and info.activelines then funcs_hit[info.func] = true; for line in pairs(info.activelines) do lines_hit[line] = false; -- Marks it as hittable, but not hit yet end end if lines_hit[line] == false then --print("New line hit: "..line.." in "..debug.getinfo(2, "S").source); lines_hit[line] = true; covered_lines = covered_lines + 1; end end end, function (test_name, success) -- Get info local fn = file:gsub("^%W*", ""); local total_active_lines = 0; local coverage_file = io.open("reports/coverage_"..fn:gsub("%W+", "_")..".report", "a+"); for line, active in pairs(lines_hit) do if active ~= nil then total_active_lines = total_active_lines + 1; end if coverage_file then if active == false then coverage_file:write(fn, "|", line, "|", name or "", "|miss\n"); else coverage_file:write(fn, "|", line, "|", name or "", "|", tostring(success), "\n"); end end end if coverage_file then coverage_file:close(); end return covered_lines, total_active_lines, lines_hit; end end run_all_tests() prosody-0.9.1/tests/util/0000775000175000017500000000000012213321667015250 5ustar matthewmatthewprosody-0.9.1/tests/util/logger.lua0000644000175000017500000000246212213321667017234 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format = string.format; local print = print; local debug = debug; local tostring = tostring; local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring; local do_pretty_printing = not os.getenv("WINDIR"); module "logger" local logstyles = {}; --TODO: This should be done in config, but we don't have proper config yet if do_pretty_printing then logstyles["info"] = getstyle("bold"); logstyles["warn"] = getstyle("bold", "yellow"); logstyles["error"] = getstyle("bold", "red"); end function init(name) --name = nil; -- While this line is not commented, will automatically fill in file/line number info return function (level, message, ...) if level == "debug" or level == "info" then return; end if not name then local inf = debug.getinfo(3, 'Snl'); level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline; end if ... then print(name, getstring(logstyles[level], level), format(message, ...)); else print(name, getstring(logstyles[level], level), message); end end end return _M; prosody-0.9.1/tests/test_core_modulemanager.lua0000644000175000017500000000463012213321667021666 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; local helpers = require "util.helpers"; local set = require "util.set"; function load_modules_for_host(load_modules_for_host, mm) local test_num = 0; local function test_load(global_modules_enabled, global_modules_disabled, host_modules_enabled, host_modules_disabled, expected_modules) test_num = test_num + 1; -- Prepare hosts = { ["example.com"] = {} }; config.set("*", "core", "modules_enabled", global_modules_enabled); config.set("*", "core", "modules_disabled", global_modules_disabled); config.set("example.com", "core", "modules_enabled", host_modules_enabled); config.set("example.com", "core", "modules_disabled", host_modules_disabled); expected_modules = set.new(expected_modules); expected_modules:add_list(helpers.get_upvalue(load_modules_for_host, "autoload_modules")); local loaded_modules = set.new(); function mm.load(host, module) assert_equal(host, "example.com", test_num..": Host isn't example.com but "..tostring(host)); assert_equal(expected_modules:contains(module), true, test_num..": Loading unexpected module '"..tostring(module).."'"); loaded_modules:add(module); end load_modules_for_host("example.com"); assert_equal((expected_modules - loaded_modules):empty(), true, test_num..": Not all modules loaded: "..tostring(expected_modules - loaded_modules)); end test_load({ "one", "two", "three" }, nil, nil, nil, { "one", "two", "three" }); test_load({ "one", "two", "three" }, {}, nil, nil, { "one", "two", "three" }); test_load({ "one", "two", "three" }, { "two" }, nil, nil, { "one", "three" }); test_load({ "one", "two", "three" }, { "three" }, nil, nil, { "one", "two" }); test_load({ "one", "two", "three" }, nil, nil, { "three" }, { "one", "two" }); test_load({ "one", "two", "three" }, nil, { "three" }, { "three" }, { "one", "two", "three" }); test_load({ "one", "two" }, nil, { "three" }, nil, { "one", "two", "three" }); test_load({ "one", "two", "three" }, nil, { "three" }, nil, { "one", "two", "three" }); test_load({ "one", "two", "three" }, { "three" }, { "three" }, nil, { "one", "two", "three" }); test_load({ "one", "two" }, { "three" }, { "three" }, nil, { "one", "two", "three" }); end prosody-0.9.1/tests/test_util_multitable.lua0000644000175000017500000000327212213321667021236 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function new(new, multitable) mt = new(); assert_table(mt, "Multitable is a table"); assert_function(mt.add, "Multitable has method add"); assert_function(mt.get, "Multitable has method get"); assert_function(mt.remove, "Multitable has method remove"); get(mt.get, multitable); end function get(get, multitable) local function has_items(list, ...) local should_have = {}; if select('#', ...) > 0 then assert_table(list, "has_items: list is table", 3); else assert_is_not(list and #list > 0, "No items, and no list"); return true, "has-all"; end for n=1,select('#', ...) do should_have[select(n, ...)] = true; end for n, item in ipairs(list) do if not should_have[item] then return false, "too-many"; end should_have[item] = nil; end if next(should_have) then return false, "not-enough"; end return true, "has-all"; end local function assert_has_all(message, list, ...) return assert_equal(select(2, has_items(list, ...)), "has-all", message or "List has all expected items, and no more", 2); end mt = multitable.new(); local trigger1, trigger2, trigger3 = {}, {}, {}; local item1, item2, item3 = {}, {}, {}; assert_has_all("Has no items with trigger1", mt:get(trigger1)); mt:add(1, 2, 3, item1); assert_has_all("Has item1 for 1, 2, 3", mt:get(1, 2, 3), item1); -- Doesn't support nil --[[ mt:add(nil, item1); mt:add(nil, item2); mt:add(nil, item3); assert_has_all("Has all items with (nil)", mt:get(nil), item1, item2, item3); ]] end prosody-0.9.1/tests/run_tests.sh0000755000175000017500000000005612213321667016657 0ustar matthewmatthew#!/bin/sh rm reports/*.report lua test.lua $* prosody-0.9.1/tests/test_core_s2smanager.lua0000644000175000017500000000250312213321667021105 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function compare_srv_priorities(csp) local r1 = { priority = 10, weight = 0 } local r2 = { priority = 100, weight = 0 } local r3 = { priority = 1000, weight = 2 } local r4 = { priority = 1000, weight = 2 } local r5 = { priority = 1000, weight = 5 } assert_equal(csp(r1, r1), false); assert_equal(csp(r1, r2), true); assert_equal(csp(r1, r3), true); assert_equal(csp(r1, r4), true); assert_equal(csp(r1, r5), true); assert_equal(csp(r2, r1), false); assert_equal(csp(r2, r2), false); assert_equal(csp(r2, r3), true); assert_equal(csp(r2, r4), true); assert_equal(csp(r2, r5), true); assert_equal(csp(r3, r1), false); assert_equal(csp(r3, r2), false); assert_equal(csp(r3, r3), false); assert_equal(csp(r3, r4), false); assert_equal(csp(r3, r5), false); assert_equal(csp(r4, r1), false); assert_equal(csp(r4, r2), false); assert_equal(csp(r4, r3), false); assert_equal(csp(r4, r4), false); assert_equal(csp(r4, r5), false); assert_equal(csp(r5, r1), false); assert_equal(csp(r5, r2), false); assert_equal(csp(r5, r3), true); assert_equal(csp(r5, r4), true); assert_equal(csp(r5, r5), false); end prosody-0.9.1/tests/modulemanager_option_conversion.lua0000644000175000017500000000451312213321667023454 0ustar matthewmatthewpackage.path = "../?.lua;"..package.path; local api = require "core.modulemanager".api; local module = setmetatable({}, {__index = api}); local opt = nil; function module:log() end function module:get_option(name) if name == "opt" then return opt; else return nil; end end function test_value(value, returns) opt = value; assert(module:get_option_number("opt") == returns.number, "number doesn't match"); assert(module:get_option_string("opt") == returns.string, "string doesn't match"); assert(module:get_option_boolean("opt") == returns.boolean, "boolean doesn't match"); if type(returns.array) == "table" then local target_array, returned_array = returns.array, module:get_option_array("opt"); assert(#target_array == #returned_array, "array length doesn't match"); for i=1,#target_array do assert(target_array[i] == returned_array[i], "array item doesn't match"); end else assert(module:get_option_array("opt") == returns.array, "array is returned (not nil)"); end if type(returns.set) == "table" then local target_items, returned_items = set.new(returns.set), module:get_option_set("opt"); assert(target_items == returned_items, "set doesn't match"); else assert(module:get_option_set("opt") == returns.set, "set is returned (not nil)"); end end test_value(nil, {}); test_value(true, { boolean = true, string = "true", array = {true}, set = {true} }); test_value(false, { boolean = false, string = "false", array = {false}, set = {false} }); test_value("true", { boolean = true, string = "true", array = {"true"}, set = {"true"} }); test_value("false", { boolean = false, string = "false", array = {"false"}, set = {"false"} }); test_value(1, { boolean = true, string = "1", array = {1}, set = {1}, number = 1 }); test_value(0, { boolean = false, string = "0", array = {0}, set = {0}, number = 0 }); test_value("hello world", { string = "hello world", array = {"hello world"}, set = {"hello world"} }); test_value(1234, { string = "1234", number = 1234, array = {1234}, set = {1234} }); test_value({1, 2, 3}, { boolean = true, string = "1", number = 1, array = {1, 2, 3}, set = {1, 2, 3} }); test_value({1, 2, 3, 3, 4}, {boolean = true, string = "1", number = 1, array = {1, 2, 3, 3, 4}, set = {1, 2, 3, 4} }); test_value({0, 1, 2, 3}, { boolean = false, string = "0", number = 0, array = {0, 1, 2, 3}, set = {0, 1, 2, 3} }); prosody-0.9.1/tests/test_util_stanza.lua0000644000175000017500000000210312213321667020364 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function preserialize(preserialize, st) local stanza = st.stanza("message", { a = "a" }); local stanza2 = preserialize(stanza); assert_is(stanza2 and stanza.name, "preserialize returns a stanza"); assert_is_not(stanza2.tags, "Preserialized stanza has no tag list"); assert_is_not(stanza2.last_add, "Preserialized stanza has no last_add marker"); assert_is_not(getmetatable(stanza2), "Preserialized stanza has no metatable"); end function deserialize(deserialize, st) local stanza = st.stanza("message", { a = "a" }); local stanza2 = deserialize(st.preserialize(stanza)); assert_is(stanza2 and stanza.name, "deserialize returns a stanza"); assert_table(stanza2.attr, "Deserialized stanza has attributes"); assert_equal(stanza2.attr.a, "a", "Deserialized stanza retains attributes"); assert_table(getmetatable(stanza2), "Deserialized stanza has metatable"); end prosody-0.9.1/tests/reports/0000775000175000017500000000000012213321667015771 5ustar matthewmatthewprosody-0.9.1/tests/reports/empty0000644000175000017500000000005012213321667017043 0ustar matthewmatthewThis file was intentionally left blank. prosody-0.9.1/tests/test_core_configmanager.lua0000644000175000017500000000315112213321667021643 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function get(get, config) config.set("example.com", "test", "testkey", 123); assert_equal(get("example.com", "test", "testkey"), 123, "Retrieving a set key"); config.set("*", "test", "testkey1", 321); assert_equal(get("*", "test", "testkey1"), 321, "Retrieving a set global key"); assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists"); config.set("example.com", "test", ""); -- Creates example.com host in config assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists"); assert_equal(get(), nil, "No parameters to get()"); assert_equal(get("undefined host"), nil, "Getting for undefined host"); assert_equal(get("undefined host", "undefined section"), nil, "Getting for undefined host & section"); assert_equal(get("undefined host", "undefined section", "undefined key"), nil, "Getting for undefined host & section & key"); assert_equal(get("example.com", "undefined section", "testkey"), nil, "Defined host, undefined section"); end function set(set, u) assert_equal(set("*"), false, "Set with no section/key"); assert_equal(set("*", "set_test"), false, "Set with no key"); assert_equal(set("*", "set_test", "testkey"), true, "Setting a nil global value"); assert_equal(set("*", "set_test", "testkey", 123), true, "Setting a global value"); end prosody-0.9.1/tests/test_core_stanza_router.lua0000644000175000017500000002374412213321667021755 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- _G.prosody = { full_sessions = {}; bare_sessions = {}; hosts = {}; }; function core_process_stanza(core_process_stanza, u) local stanza = require "util.stanza"; local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" } local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } } local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session } } local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" } _G.prosody.hosts["localhost"] = local_host_session; _G.prosody.full_sessions["user@localhost/resource"] = local_user_session; _G.prosody.bare_sessions["user@localhost"] = { sessions = { resource = local_user_session } }; -- Test message routing local function test_message_full_jid() local env = testlib_new_env(); local msg = stanza.stanza("message", { to = "user@localhost/resource", type = "chat" }):tag("body"):text("Hello world"); local target_routed; function env.core_post_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct"); assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end env.hosts = hosts; env.prosody = { hosts = hosts }; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end local function test_message_bare_jid() local env = testlib_new_env(); local msg = stanza.stanza("message", { to = "user@localhost", type = "chat" }):tag("body"):text("Hello world"); local target_routed; function env.core_post_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct"); assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end local function test_message_no_to() local env = testlib_new_env(); local msg = stanza.stanza("message", { type = "chat" }):tag("body"):text("Hello world"); local target_handled; function env.core_post_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_handled = true; end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_handled, true, "stanza was not handled successfully"); end local function test_message_to_remote_bare() local env = testlib_new_env(); local msg = stanza.stanza("message", { to = "user@remotehost", type = "chat" }):tag("body"):text("Hello world"); local target_routed; function env.core_route_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end function env.core_post_stanza(...) env.core_route_stanza(...); end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end local function test_message_to_remote_server() local env = testlib_new_env(); local msg = stanza.stanza("message", { to = "remotehost", type = "chat" }):tag("body"):text("Hello world"); local target_routed; function env.core_route_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end function env.core_post_stanza(...) env.core_route_stanza(...); end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end --IQ tests local function test_iq_to_remote_server() local env = testlib_new_env(); local msg = stanza.stanza("iq", { to = "remotehost", type = "get", id = "id" }):tag("body"):text("Hello world"); local target_routed; function env.core_route_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end function env.core_post_stanza(...) env.core_route_stanza(...); end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end local function test_iq_error_to_local_user() local env = testlib_new_env(); local msg = stanza.stanza("iq", { to = "user@localhost/resource", from = "user@remotehost", type = "error", id = "id" }):tag("error", { type = 'cancel' }):tag("item-not-found", { xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' }); local target_routed; function env.core_route_stanza(p_origin, p_stanza) assert_equal(p_origin, s2sin_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_routed = true; end function env.core_post_stanza(...) env.core_route_stanza(...); end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(s2sin_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_routed, true, "stanza was not routed successfully"); end local function test_iq_to_local_bare() local env = testlib_new_env(); local msg = stanza.stanza("iq", { to = "user@localhost", from = "user@localhost", type = "get", id = "id" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); local target_handled; function env.core_post_stanza(p_origin, p_stanza) assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct"); assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print()); target_handled = true; end env.hosts = hosts; setfenv(core_process_stanza, env); assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value"); assert_equal(target_handled, true, "stanza was not handled successfully"); end runtest(test_message_full_jid, "Messages with full JID destinations get routed"); runtest(test_message_bare_jid, "Messages with bare JID destinations get routed"); runtest(test_message_no_to, "Messages with no destination are handled by the server"); runtest(test_message_to_remote_bare, "Messages to a remote user are routed by the server"); runtest(test_message_to_remote_server, "Messages to a remote server's JID are routed"); runtest(test_iq_to_remote_server, "iq to a remote server's JID are routed"); runtest(test_iq_to_local_bare, "iq from a local user to a local user's bare JID are handled"); runtest(test_iq_error_to_local_user, "iq type=error to a local user's JID are routed"); end function core_route_stanza(core_route_stanza) local stanza = require "util.stanza"; local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" } local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } } local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session }, sessions = {} } local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" } local hosts = { ["localhost"] = local_host_session; } local function test_iq_result_to_offline_user() local env = testlib_new_env(); local msg = stanza.stanza("iq", { to = "user@localhost/foo", from = "user@localhost", type = "result" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); local msg2 = stanza.stanza("iq", { to = "user@localhost/foo", from = "user@localhost", type = "error" }):tag("ping", { xmlns = "urn:xmpp:ping:0" }); --package.loaded["core.usermanager"] = { user_exists = function (user, host) print("RAR!") return true or user == "user" and host == "localhost" and true; end }; local target_handled, target_replied; function env.core_post_stanza(p_origin, p_stanza) target_handled = true; end function local_user_session.send(data) --print("Replying with: ", tostring(data)); --print(debug.traceback()) target_replied = true; end env.hosts = hosts; setfenv(core_route_stanza, env); assert_equal(core_route_stanza(local_user_session, msg), nil, "core_route_stanza returned incorrect value"); assert_equal(target_handled, nil, "stanza was handled and not dropped"); assert_equal(target_replied, nil, "stanza was replied to and not dropped"); package.loaded["core.usermanager"] = nil; end --runtest(test_iq_result_to_offline_user, "iq type=result|error to an offline user are not replied to"); end prosody-0.9.1/tests/test_util_jid.lua0000644000175000017500000000674512213321667017652 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function join(join) assert_equal(join("a", "b", "c"), "a@b/c", "builds full JID"); assert_equal(join("a", "b", nil), "a@b", "builds bare JID"); assert_equal(join(nil, "b", "c"), "b/c", "builds full host JID"); assert_equal(join(nil, "b", nil), "b", "builds bare host JID"); assert_equal(join(nil, nil, nil), nil, "invalid JID is nil"); assert_equal(join("a", nil, nil), nil, "invalid JID is nil"); assert_equal(join(nil, nil, "c"), nil, "invalid JID is nil"); assert_equal(join("a", nil, "c"), nil, "invalid JID is nil"); end function split(split) function test(input_jid, expected_node, expected_server, expected_resource) local rnode, rserver, rresource = split(input_jid); assert_equal(expected_node, rnode, "split("..tostring(input_jid)..") failed"); assert_equal(expected_server, rserver, "split("..tostring(input_jid)..") failed"); assert_equal(expected_resource, rresource, "split("..tostring(input_jid)..") failed"); end -- Valid JIDs test("node@server", "node", "server", nil ); test("node@server/resource", "node", "server", "resource" ); test("server", nil, "server", nil ); test("server/resource", nil, "server", "resource" ); test("server/resource@foo", nil, "server", "resource@foo" ); test("server/resource@foo/bar", nil, "server", "resource@foo/bar"); -- Always invalid JIDs test(nil, nil, nil, nil); test("node@/server", nil, nil, nil); test("@server", nil, nil, nil); test("@server/resource", nil, nil, nil); test("@/resource", nil, nil, nil); end function bare(bare) assert_equal(bare("user@host"), "user@host", "bare JID remains bare"); assert_equal(bare("host"), "host", "Host JID remains host"); assert_equal(bare("host/resource"), "host", "Host JID with resource becomes host"); assert_equal(bare("user@host/resource"), "user@host", "user@host JID with resource becomes user@host"); assert_equal(bare("user@/resource"), nil, "invalid JID is nil"); assert_equal(bare("@/resource"), nil, "invalid JID is nil"); assert_equal(bare("@/"), nil, "invalid JID is nil"); assert_equal(bare("/"), nil, "invalid JID is nil"); assert_equal(bare(""), nil, "invalid JID is nil"); assert_equal(bare("@"), nil, "invalid JID is nil"); assert_equal(bare("user@"), nil, "invalid JID is nil"); assert_equal(bare("user@@"), nil, "invalid JID is nil"); assert_equal(bare("user@@host"), nil, "invalid JID is nil"); assert_equal(bare("user@@host/resource"), nil, "invalid JID is nil"); assert_equal(bare("user@host/"), nil, "invalid JID is nil"); end function compare(compare) assert_equal(compare("host", "host"), true, "host should match"); assert_equal(compare("host", "other-host"), false, "host should not match"); assert_equal(compare("other-user@host/resource", "host"), true, "host should match"); assert_equal(compare("other-user@host", "user@host"), false, "user should not match"); assert_equal(compare("user@host", "host"), true, "host should match"); assert_equal(compare("user@host/resource", "host"), true, "host should match"); assert_equal(compare("user@host/resource", "user@host"), true, "user and host should match"); assert_equal(compare("user@other-host", "host"), false, "host should not match"); assert_equal(compare("user@other-host", "user@host"), false, "host should not match"); end prosody-0.9.1/tests/test_sasl.lua0000644000175000017500000000173112213321667016777 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local gmatch = string.gmatch; local t_concat, t_insert = table.concat, table.insert; local to_byte, to_char = string.byte, string.char; local function _latin1toutf8(str) if not str then return str; end local p = {}; for ch in gmatch(str, ".") do ch = to_byte(ch); if (ch < 0x80) then t_insert(p, to_char(ch)); elseif (ch < 0xC0) then t_insert(p, to_char(0xC2, ch)); else t_insert(p, to_char(0xC3, ch - 64)); end end return t_concat(p); end function latin1toutf8() local function assert_utf8(latin, utf8) assert_equal(_latin1toutf8(latin), utf8, "Incorrect UTF8 from Latin1: "..tostring(latin)); end assert_utf8("", "") assert_utf8("test", "test") assert_utf8(nil, nil) assert_utf8("foobar.r\229kat.se", "foobar.r\195\165kat.se") end prosody-0.9.1/tests/test_util_rfc3484.lua0000644000175000017500000000726312213321667020175 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2011 Florian Zeitz -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- function source(source) local new_ip = require"util.ip".new_ip; assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope"); assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope"); assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope"); assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope"); assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address"); assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope"); assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix"); assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label"); end function destination(dest) local order; local new_ip = require"util.ip".new_ip; order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")}) assert_equal(order[1].addr, "2001::1", "prefer matching scope"); assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope") order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")}) assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope") assert_equal(order[2].addr, "2001::1", "prefer matching scope") order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")}) assert_equal(order[1].addr, "2001::1", "prefer higher precedence"); assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence"); order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")}) assert_equal(order[1].addr, "fe80::1", "prefer smaller scope"); assert_equal(order[2].addr, "fec0::1", "prefer smaller scope"); assert_equal(order[3].addr, "2001::1", "prefer smaller scope"); order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert_equal(order[1].addr, "2001::1", "longest matching prefix"); assert_equal(order[2].addr, "3ffe::1", "longest matching prefix"); order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label"); assert_equal(order[2].addr, "2001::1", "prefer matching label"); order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")}) assert_equal(order[1].addr, "2001::1", "prefer higher precedence"); assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence"); end prosody-0.9.1/certs/0000775000175000017500000000000012213321667014251 5ustar matthewmatthewprosody-0.9.1/certs/localhost.key0000644000175000017500000000156712213321667016762 0ustar matthewmatthew-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDeZ+cu/gO1elmeZ//p+Hqox4kPqdFw9QWANuN+QIMyhEfopZqw hb+fWllSIt40tGAd4KDJrkpPDy7/KYv64xCmIcw6IrqFvy784yuaVc2xTDZZFuSN LeC6429pTJMW0K/wf5NO0Q5SSSUU5q9o+De7zKM51RW40GOnUQL97L2GRQIDAQAB AoGAYaWw5Pr12en8CwaSX8GO6SeiT9Q5dqS9Y4u12iqs77MQd16uSi6O8YITkXJp qS5AvR1wutvhGFEMS0+Me/zRw62OFc2VVrKmX6eqgRMR8d/+SZjqzUxb4pNIAPQU dHbQzqGXermf6UWm6Cbi7vN0diohd8Qoj98PeWfRQrXju0kCQQD3OXD2SEevEhNe g4YTREsyUkZV1etkldhAeDAJzlitCQdQF5zE9Wt/Ahv0BKlLTaz3mvSDwrI+lXYQ 1iDzOrXrAkEA5kzu1A3Y2gclyRupTg7crgp+afh1fLKCIVUaFdOYgwQDX90YnnIq TaY4uQ8Eutoixha4ZM4/bJq17YjjY1O4jwJAZMEHNYftlv7h3/HwMWfy0XZQbej5 vwuGj3er9EMhRpvYXB7TaD2w6pkcdU11BViJtntzTUOKyxC0hlYOJbJ2swJAOL3N vhtnSVine6RAE4Zf4tWdDdj0gXOt0i6YjbYjhmwvtKfR0AAK4jTJFvdXT/48wReJ +PRD9issFck7VRakiwJAPTgFUTsFCR1ZPcuCPHSCK/wz2NFma/O5Eqm0qTIbNUfw 3qDRyUuKbyr3bAc+K+asN5ok2PAnhiRUIpu146M17w== -----END RSA PRIVATE KEY----- prosody-0.9.1/certs/openssl.cnf0000644000175000017500000000273712213321667016433 0ustar matthewmatthewoid_section = new_oids [ new_oids ] # RFC 6120 section 13.7.1.4. defines this OID xmppAddr = 1.3.6.1.5.5.7.8.5 # RFC 4985 defines this OID SRVName = 1.3.6.1.5.5.7.8.7 [ req ] default_bits = 4096 default_keyfile = example.com.key distinguished_name = distinguished_name req_extensions = v3_extensions x509_extensions = v3_extensions # ask about the DN? prompt = no [ distinguished_name ] commonName = example.com countryName = GB localityName = The Internet organizationName = Your Organisation organizationalUnitName = XMPP Department emailAddress = xmpp@example.com [ v3_extensions ] # for certificate requests (req_extensions) # and self-signed certificates (x509_extensions) basicConstraints = CA:FALSE keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth,clientAuth subjectAltName = @subject_alternative_name [ subject_alternative_name ] # See http://tools.ietf.org/html/rfc6120#section-13.7.1.2 for more info. DNS.0 = example.com otherName.0 = xmppAddr;FORMAT:UTF8,UTF8:example.com otherName.1 = SRVName;IA5STRING:_xmpp-client.example.com otherName.2 = SRVName;IA5STRING:_xmpp-server.example.com DNS.1 = conference.example.com otherName.3 = xmppAddr;FORMAT:UTF8,UTF8:conference.example.com otherName.4 = SRVName;IA5STRING:_xmpp-server.conference.example.com prosody-0.9.1/certs/Makefile0000644000175000017500000000163712213321667015716 0ustar matthewmatthew.DEFAULT: localhost.crt keysize=2048 # How to: # First, `make yourhost.cnf` which creates a openssl config file. # Then edit this file and fill in the details you want it to have, # and add or change hosts and components it should cover. # Then `make yourhost.key` to create your private key, you can # include keysize=number to change the size of the key. # Then you can either `make yourhost.csr` to generate a certificate # signing request that you can submit to a CA, or `make yourhost.crt` # to generate a self signed certificate. .PRECIOUS: %.cnf %.key # To request a cert %.csr: %.cnf %.key openssl req -new -key $(lastword $^) -out $@ -utf8 -config $(firstword $^) # Self signed %.crt: %.cnf %.key openssl req -new -x509 -nodes -key $(lastword $^) -days 365 \ -sha1 -out $@ -utf8 -config $(firstword $^) %.cnf: sed 's,example\.com,$*,g' openssl.cnf > $@ %.key: openssl genrsa $(keysize) > $@ @chmod 400 $@ prosody-0.9.1/certs/localhost.crt0000644000175000017500000000245212213321667016754 0ustar matthewmatthew-----BEGIN CERTIFICATE----- MIIDojCCAwugAwIBAgIJAPO1OI+vmUi8MA0GCSqGSIb3DQEBBQUAMIGTMQswCQYD VQQGEwJHQjETMBEGA1UECBMKSmFiYmVybGFuZDETMBEGA1UEChMKUHJvc29keSBJ TTE8MDoGA1UECxQzaHR0cDovL3Byb3NvZHkuaW0vZG9jL2FkdmFuY2VkX3NzbF90 bHMjY2VydGlmaWNhdGVzMRwwGgYDVQQDExNFeGFtcGxlIGNlcnRpZmljYXRlMB4X DTA5MTAxNzE3MDc1NloXDTEwMTAxNzE3MDc1NlowgZMxCzAJBgNVBAYTAkdCMRMw EQYDVQQIEwpKYWJiZXJsYW5kMRMwEQYDVQQKEwpQcm9zb2R5IElNMTwwOgYDVQQL FDNodHRwOi8vcHJvc29keS5pbS9kb2MvYWR2YW5jZWRfc3NsX3RscyNjZXJ0aWZp Y2F0ZXMxHDAaBgNVBAMTE0V4YW1wbGUgY2VydGlmaWNhdGUwgZ8wDQYJKoZIhvcN AQEBBQADgY0AMIGJAoGBAN5n5y7+A7V6WZ5n/+n4eqjHiQ+p0XD1BYA2435AgzKE R+ilmrCFv59aWVIi3jS0YB3goMmuSk8PLv8pi/rjEKYhzDoiuoW/LvzjK5pVzbFM NlkW5I0t4Lrjb2lMkxbQr/B/k07RDlJJJRTmr2j4N7vMoznVFbjQY6dRAv3svYZF AgMBAAGjgfswgfgwHQYDVR0OBBYEFJhMTxNc3LEYA1vm3v4sCdHzRnUDMIHIBgNV HSMEgcAwgb2AFJhMTxNc3LEYA1vm3v4sCdHzRnUDoYGZpIGWMIGTMQswCQYDVQQG EwJHQjETMBEGA1UECBMKSmFiYmVybGFuZDETMBEGA1UEChMKUHJvc29keSBJTTE8 MDoGA1UECxQzaHR0cDovL3Byb3NvZHkuaW0vZG9jL2FkdmFuY2VkX3NzbF90bHMj Y2VydGlmaWNhdGVzMRwwGgYDVQQDExNFeGFtcGxlIGNlcnRpZmljYXRlggkA87U4 j6+ZSLwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCtLrTOSpQn+j+/ 5zoiP5wAGLpdZE+Iatzd26QwVsL61zd5399nEb1yFs3Hl9jo4W3idyNoofa67atX 2/+3juA0Q/oN/ZT16bWihmcrzv+Qd/CsQfMOZ5ApYV4SEw40L6GITtrZuBDjO4mU TavhtScoGRzrZavhJG+PyhDH0Scglg== -----END CERTIFICATE----- prosody-0.9.1/prosody.release0000664000175000017500000000000612213321667016166 0ustar matthewmatthew0.9.1 prosody-0.9.1/core/0000775000175000017500000000000012213321667014061 5ustar matthewmatthewprosody-0.9.1/core/loggingmanager.lua0000644000175000017500000001667012213321667017555 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local format = string.format; local setmetatable, rawset, pairs, ipairs, type = setmetatable, rawset, pairs, ipairs, type; local io_open, io_write = io.open, io.write; local math_max, rep = math.max, string.rep; local os_date = os.date; local getstyle, setstyle = require "util.termcolours".getstyle, require "util.termcolours".setstyle; if os.getenv("__FLUSH_LOG") then local io_flush = io.flush; local _io_write = io_write; io_write = function(...) _io_write(...); io_flush(); end end local config = require "core.configmanager"; local logger = require "util.logger"; local prosody = prosody; _G.log = logger.init("general"); module "loggingmanager" -- The log config used if none specified in the config file (see reload_logging for initialization) local default_logging; local default_file_logging; local default_timestamp = "%b %d %H:%M:%S"; -- The actual config loggingmanager is using local logging_config; local apply_sink_rules; local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; }); local get_levels; local logging_levels = { "debug", "info", "warn", "error" } -- Put a rule into action. Requires that the sink type has already been registered. -- This function is called automatically when a new sink type is added [see apply_sink_rules()] local function add_rule(sink_config) local sink_maker = log_sink_types[sink_config.to]; if sink_maker then -- Create sink local sink = sink_maker(sink_config); -- Set sink for all chosen levels for level in pairs(get_levels(sink_config.levels or logging_levels)) do logger.add_level_sink(level, sink); end else -- No such sink type end end -- Search for all rules using a particular sink type, and apply -- them. Called automatically when a new sink type is added to -- the log_sink_types table. function apply_sink_rules(sink_type) if type(logging_config) == "table" then for _, level in ipairs(logging_levels) do if type(logging_config[level]) == "string" then local value = logging_config[level]; if sink_type == "file" and not value:match("^%*") then add_rule({ to = sink_type; filename = value; timestamps = true; levels = { min = level }; }); elseif value == "*"..sink_type then add_rule({ to = sink_type; levels = { min = level }; }); end end end for _, sink_config in ipairs(logging_config) do if (type(sink_config) == "table" and sink_config.to == sink_type) then add_rule(sink_config); elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then add_rule({ levels = { min = "debug" }, to = sink_type }); end end elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then -- User specified simply a filename, and the "file" sink type -- was just added for _, sink_config in pairs(default_file_logging) do sink_config.filename = logging_config; add_rule(sink_config); sink_config.filename = nil; end elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then -- Log all levels (debug+) to this sink add_rule({ levels = { min = "debug" }, to = sink_type }); end end --- Helper function to get a set of levels given a "criteria" table function get_levels(criteria, set) set = set or {}; if type(criteria) == "string" then set[criteria] = true; return set; end local min, max = criteria.min, criteria.max; if min or max then local in_range; for _, level in ipairs(logging_levels) do if min == level then set[level] = true; in_range = true; elseif max == level then set[level] = true; return set; elseif in_range then set[level] = true; end end end for _, level in ipairs(criteria) do set[level] = true; end return set; end -- Initialize config, etc. -- function reload_logging() local old_sink_types = {}; for name, sink_maker in pairs(log_sink_types) do old_sink_types[name] = sink_maker; log_sink_types[name] = nil; end logger.reset(); local debug_mode = config.get("*", "debug"); default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } }; default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } }; default_timestamp = "%b %d %H:%M:%S"; logging_config = config.get("*", "log") or default_logging; for name, sink_maker in pairs(old_sink_types) do log_sink_types[name] = sink_maker; end prosody.events.fire_event("logging-reloaded"); end reload_logging(); prosody.events.add_handler("config-reloaded", reload_logging); --- Definition of built-in logging sinks --- -- Null sink, must enter log_sink_types *first* function log_sink_types.nowhere() return function () return false; end; end -- Column width for "source" (used by stdout and console) local sourcewidth = 20; function log_sink_types.stdout(config) local timestamps = config.timestamps; if timestamps == true then timestamps = default_timestamp; -- Default format end return function (name, level, message, ...) sourcewidth = math_max(#name+2, sourcewidth); local namelen = #name; if timestamps then io_write(os_date(timestamps), " "); end if ... then io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n"); else io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n"); end end end do local do_pretty_printing = true; local logstyles = {}; if do_pretty_printing then logstyles["info"] = getstyle("bold"); logstyles["warn"] = getstyle("bold", "yellow"); logstyles["error"] = getstyle("bold", "red"); end function log_sink_types.console(config) -- Really if we don't want pretty colours then just use plain stdout if not do_pretty_printing then return log_sink_types.stdout(config); end local timestamps = config.timestamps; if timestamps == true then timestamps = default_timestamp; -- Default format end return function (name, level, message, ...) sourcewidth = math_max(#name+2, sourcewidth); local namelen = #name; if timestamps then io_write(os_date(timestamps), " "); end io_write(name, rep(" ", sourcewidth-namelen)); setstyle(logstyles[level]); io_write(level); setstyle(); if ... then io_write("\t", format(message, ...), "\n"); else io_write("\t", message, "\n"); end end end end local empty_function = function () end; function log_sink_types.file(config) local log = config.filename; local logfile = io_open(log, "a+"); if not logfile then return empty_function; end local write, flush = logfile.write, logfile.flush; local timestamps = config.timestamps; if timestamps == nil or timestamps == true then timestamps = default_timestamp; -- Default format end return function (name, level, message, ...) if timestamps then write(logfile, os_date(timestamps), " "); end if ... then write(logfile, name, "\t", level, "\t", format(message, ...), "\n"); else write(logfile, name, "\t" , level, "\t", message, "\n"); end flush(logfile); end; end function register_sink_type(name, sink_maker) local old_sink_maker = log_sink_types[name]; log_sink_types[name] = sink_maker; return old_sink_maker; end return _M; prosody-0.9.1/core/certmanager.lua0000644000175000017500000001212412213321667017052 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local configmanager = require "core.configmanager"; local log = require "util.logger".init("certmanager"); local ssl = ssl; local ssl_newcontext = ssl and ssl.newcontext; local tostring = tostring; local type = type; local io_open = io.open; local prosody = prosody; local resolve_path = configmanager.resolve_relative_path; local config_path = prosody.paths.config; local luasec_has_noticket, luasec_has_verifyext, luasec_has_no_compression; if ssl then local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)"); luasec_has_noticket = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=4; luasec_has_verifyext = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5; luasec_has_no_compression = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5; end module "certmanager" -- Global SSL options if not overridden per-host local default_ssl_config = configmanager.get("*", "ssl"); local default_capath = "/etc/ssl/certs"; local default_verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none"; local default_options = { "no_sslv2", luasec_has_noticket and "no_ticket" or nil }; local default_verifyext = { "lsec_continue", "lsec_ignore_purpose" }; if ssl and not luasec_has_verifyext and ssl.x509 then -- COMPAT mw/luasec-hg for i=1,#default_verifyext do -- Remove lsec_ prefix default_verify[#default_verify+1] = default_verifyext[i]:sub(6); end end if luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true then default_options[#default_options+1] = "no_compression"; end if luasec_has_no_compression then -- Has no_compression? Then it has these too... default_options[#default_options+1] = "single_dh_use"; default_options[#default_options+1] = "single_ecdh_use"; end function create_context(host, mode, user_ssl_config) user_ssl_config = user_ssl_config or default_ssl_config; if not ssl then return nil, "LuaSec (required for encryption) was not found"; end if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end local ssl_config = { mode = mode; protocol = user_ssl_config.protocol or "sslv23"; key = resolve_path(config_path, user_ssl_config.key); password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end; certificate = resolve_path(config_path, user_ssl_config.certificate); capath = resolve_path(config_path, user_ssl_config.capath or default_capath); cafile = resolve_path(config_path, user_ssl_config.cafile); verify = user_ssl_config.verify or default_verify; verifyext = user_ssl_config.verifyext or default_verifyext; options = user_ssl_config.options or default_options; depth = user_ssl_config.depth; curve = user_ssl_config.curve or "secp384r1"; ciphers = user_ssl_config.ciphers or "HIGH:!DSS:!aNULL@STRENGTH"; dhparam = user_ssl_config.dhparam; }; -- LuaSec expects dhparam to be a callback that takes two arguments. -- We ignore those because it is mostly used for having a separate -- set of params for EXPORT ciphers, which we don't have by default. if type(ssl_config.dhparam) == "string" then local f, err = io_open(resolve_path(config_path, ssl_config.dhparam)); if not f then return nil, "Could not open DH parameters: "..err end local dhparam = f:read("*a"); f:close(); ssl_config.dhparam = function() return dhparam; end end local ctx, err = ssl_newcontext(ssl_config); -- COMPAT: LuaSec 0.4.1 ignores the cipher list from the config, so we have to take -- care of it ourselves... if ctx and ssl_config.ciphers then local success; success, err = ssl.context.setcipher(ctx, ssl_config.ciphers); if not success then ctx = nil; end end if not ctx then err = err or "invalid ssl config" local file = err:match("^error loading (.-) %("); if file then if file == "private key" then file = ssl_config.key or "your private key"; elseif file == "certificate" then file = ssl_config.certificate or "your certificate file"; end local reason = err:match("%((.+)%)$") or "some reason"; if reason == "Permission denied" then reason = "Check that the permissions allow Prosody to read this file."; elseif reason == "No such file or directory" then reason = "Check that the path is correct, and the file exists."; elseif reason == "system lib" then reason = "Previous error (see logs), or other system error."; elseif reason == "(null)" or not reason then reason = "Check that the file exists and the permissions are correct"; else reason = "Reason: "..tostring(reason):lower(); end log("error", "SSL/TLS: Failed to load '%s': %s (for %s)", file, reason, host); else log("error", "SSL/TLS: Error initialising for %s: %s", host, err); end end return ctx, err; end function reload_ssl_config() default_ssl_config = configmanager.get("*", "ssl"); end prosody.events.add_handler("config-reloaded", reload_ssl_config); return _M; prosody-0.9.1/core/sessionmanager.lua0000644000175000017500000001570712213321667017612 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local tostring, setmetatable = tostring, setmetatable; local pairs, next= pairs, next; local hosts = hosts; local full_sessions = full_sessions; local bare_sessions = bare_sessions; local logger = require "util.logger"; local log = logger.init("sessionmanager"); local rm_load_roster = require "core.rostermanager".load_roster; local config_get = require "core.configmanager".get; local resourceprep = require "util.encodings".stringprep.resourceprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local uuid_generate = require "util.uuid".generate; local initialize_filters = require "util.filters".initialize; local gettime = require "socket".gettime; module "sessionmanager" function new_session(conn) local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() }; local filter = initialize_filters(session); local w = conn.write; session.send = function (t) if t.name then t = filter("stanzas/out", t); end if t then t = filter("bytes/out", tostring(t)); if t then return w(conn, t); end end end session.ip = conn:ip(); local conn_name = "c2s"..tostring(session):match("[a-f0-9]+$"); session.log = logger.init(conn_name); return session; end local resting_session = { -- Resting, not dead destroyed = true; type = "c2s_destroyed"; close = function (session) session.log("debug", "Attempt to close already-closed session"); end; filter = function (type, data) return data; end; }; resting_session.__index = resting_session; function retire_session(session) local log = session.log or log; for k in pairs(session) do if k ~= "log" and k ~= "id" then session[k] = nil; end end function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end return setmetatable(session, resting_session); end function destroy_session(session, err) (session.log or log)("debug", "Destroying session for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or ""); if session.destroyed then return; end -- Remove session/resource from user's session list if session.full_jid then local host_session = hosts[session.host]; -- Allow plugins to prevent session destruction if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then return; end host_session.sessions[session.username].sessions[session.resource] = nil; full_sessions[session.full_jid] = nil; if not next(host_session.sessions[session.username].sessions) then log("debug", "All resources of %s are now offline", session.username); host_session.sessions[session.username] = nil; bare_sessions[session.username..'@'..session.host] = nil; end host_session.events.fire_event("resource-unbind", {session=session, error=err}); end retire_session(session); end function make_authenticated(session, username) username = nodeprep(username); if not username or #username == 0 then return nil, "Invalid username"; end session.username = username; if session.type == "c2s_unauthed" then session.type = "c2s"; end session.log("info", "Authenticated as %s@%s", username or "(unknown)", session.host or "(unknown)"); return true; end -- returns true, nil on success -- returns nil, err_type, err, err_message on failure function bind_resource(session, resource) if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end -- We don't support binding multiple resources resource = resourceprep(resource); resource = resource ~= "" and resource or uuid_generate(); --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing if not hosts[session.host].sessions[session.username] then local sessions = { sessions = {} }; hosts[session.host].sessions[session.username] = sessions; bare_sessions[session.username..'@'..session.host] = sessions; else local sessions = hosts[session.host].sessions[session.username].sessions; if sessions[resource] then -- Resource conflict local policy = config_get(session.host, "conflict_resolve"); local increment; if policy == "random" then resource = uuid_generate(); increment = true; elseif policy == "increment" then increment = true; -- TODO ping old resource elseif policy == "kick_new" then return nil, "cancel", "conflict", "Resource already exists"; else -- if policy == "kick_old" then sessions[resource]:close { condition = "conflict"; text = "Replaced by new connection"; }; if not next(sessions) then hosts[session.host].sessions[session.username] = { sessions = sessions }; bare_sessions[session.username.."@"..session.host] = hosts[session.host].sessions[session.username]; end end if increment and sessions[resource] then local count = 1; while sessions[resource.."#"..count] do count = count + 1; end resource = resource.."#"..count; end end end session.resource = resource; session.full_jid = session.username .. '@' .. session.host .. '/' .. resource; hosts[session.host].sessions[session.username].sessions[resource] = session; full_sessions[session.full_jid] = session; local err; session.roster, err = rm_load_roster(session.username, session.host); if err then full_sessions[session.full_jid] = nil; hosts[session.host].sessions[session.username].sessions[resource] = nil; session.full_jid = nil; session.resource = nil; if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then bare_sessions[session.username..'@'..session.host] = nil; hosts[session.host].sessions[session.username] = nil; end session.log("error", "Roster loading failed: %s", err); return nil, "cancel", "internal-server-error", "Error loading roster"; end hosts[session.host].events.fire_event("resource-bind", {session=session}); return true; end function send_to_available_resources(user, host, stanza) local jid = user.."@"..host; local count = 0; local user = bare_sessions[jid]; if user then for k, session in pairs(user.sessions) do if session.presence then session.send(stanza); count = count + 1; end end end return count; end function send_to_interested_resources(user, host, stanza) local jid = user.."@"..host; local count = 0; local user = bare_sessions[jid]; if user then for k, session in pairs(user.sessions) do if session.interested then session.send(stanza); count = count + 1; end end end return count; end return _M; prosody-0.9.1/core/storagemanager.lua0000644000175000017500000000756012213321667017571 0ustar matthewmatthew local error, type, pairs = error, type, pairs; local setmetatable = setmetatable; local config = require "core.configmanager"; local datamanager = require "util.datamanager"; local modulemanager = require "core.modulemanager"; local multitable = require "util.multitable"; local hosts = hosts; local log = require "util.logger".init("storagemanager"); local prosody = prosody; module("storagemanager") local olddm = {}; -- maintain old datamanager, for backwards compatibility for k,v in pairs(datamanager) do olddm[k] = v; end _M.olddm = olddm; local null_storage_method = function () return false, "no data storage active"; end local null_storage_driver = setmetatable( { name = "null", open = function (self) return self; end }, { __index = function (self, method) return null_storage_method; end } ); local stores_available = multitable.new(); function initialize_host(host) local host_session = hosts[host]; host_session.events.add_handler("item-added/storage-provider", function (event) local item = event.item; stores_available:set(host, item.name, item); end); host_session.events.add_handler("item-removed/storage-provider", function (event) local item = event.item; stores_available:set(host, item.name, nil); end); end prosody.events.add_handler("host-activated", initialize_host, 101); function load_driver(host, driver_name) if driver_name == "null" then return null_storage_driver; end local driver = stores_available:get(host, driver_name); if driver then return driver; end local ok, err = modulemanager.load(host, "storage_"..driver_name); if not ok then log("error", "Failed to load storage driver plugin %s on %s: %s", driver_name, host, err); end return stores_available:get(host, driver_name); end function get_driver(host, store) local storage = config.get(host, "storage"); local driver_name; local option_type = type(storage); if option_type == "string" then driver_name = storage; elseif option_type == "table" then driver_name = storage[store]; end if not driver_name then driver_name = config.get(host, "default_storage") or "internal"; end local driver = load_driver(host, driver_name); if not driver then log("warn", "Falling back to null driver for %s storage on %s", store, host); driver_name = "null"; driver = null_storage_driver; end return driver, driver_name; end function open(host, store, typ) local driver, driver_name = get_driver(host, store); local ret, err = driver:open(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 or ""); ret = null_storage_driver; err = nil; end end return ret, err; end function purge(user, host) local storage = config.get(host, "storage"); if type(storage) == "table" then -- multiple storage backends in use that we need to purge local purged = {}; for store, driver in pairs(storage) do if not purged[driver] then purged[driver] = get_driver(host, store):purge(user); end end end get_driver(host):purge(user); -- and the default driver olddm.purge(user, host); -- COMPAT list stores, like offline messages end up in the old datamanager return true; end function datamanager.load(username, host, datastore) return open(host, datastore):get(username); end function datamanager.store(username, host, datastore, data) return open(host, datastore):set(username, data); end function datamanager.users(host, datastore, typ) local driver = open(host, datastore, typ); if not driver.users then return function() log("warn", "storage driver %s does not support listing users", driver.name) end end return driver:users(); end function datamanager.stores(username, host, typ) return get_driver(host):stores(username, typ); end function datamanager.purge(username, host) return purge(username, host); end return _M; prosody-0.9.1/core/s2smanager.lua0000644000175000017500000000621212213321667016625 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local hosts = prosody.hosts; local tostring, pairs, setmetatable = tostring, pairs, setmetatable; local logger_init = require "util.logger".init; local log = logger_init("s2smanager"); local prosody = _G.prosody; incoming_s2s = {}; prosody.incoming_s2s = incoming_s2s; local incoming_s2s = incoming_s2s; local fire_event = prosody.events.fire_event; module "s2smanager" function new_incoming(conn) local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} }; session.log = logger_init("s2sin"..tostring(session):match("[a-f0-9]+$")); incoming_s2s[session] = true; return session; end function new_outgoing(from_host, to_host) local host_session = { to_host = to_host, from_host = from_host, host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" }; hosts[from_host].s2sout[to_host] = host_session; local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$"); host_session.log = logger_init(conn_name); return host_session; end local resting_session = { -- Resting, not dead destroyed = true; type = "s2s_destroyed"; open_stream = function (session) session.log("debug", "Attempt to open stream on resting session"); end; close = function (session) session.log("debug", "Attempt to close already-closed session"); end; filter = function (type, data) return data; end; }; resting_session.__index = resting_session; function retire_session(session, reason) local log = session.log or log; for k in pairs(session) do if k ~= "log" and k ~= "id" and k ~= "conn" then session[k] = nil; end end session.destruction_reason = reason; function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end return setmetatable(session, resting_session); end function destroy_session(session, reason) if session.destroyed then return; end (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or "")); if session.direction == "outgoing" then hosts[session.from_host].s2sout[session.to_host] = nil; session:bounce_sendq(reason); elseif session.direction == "incoming" then incoming_s2s[session] = nil; end local event_data = { session = session, reason = reason }; if session.type == "s2sout" then fire_event("s2sout-destroyed", event_data); if hosts[session.from_host] then hosts[session.from_host].events.fire_event("s2sout-destroyed", event_data); end elseif session.type == "s2sin" then fire_event("s2sin-destroyed", event_data); if hosts[session.to_host] then hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data); end end retire_session(session, reason); -- Clean session until it is GC'd return true; end return _M; prosody-0.9.1/core/modulemanager.lua0000644000175000017500000002357112213321667017412 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local logger = require "util.logger"; local log = logger.init("modulemanager"); local config = require "core.configmanager"; local pluginloader = require "util.pluginloader"; local set = require "util.set"; local new_multitable = require "util.multitable".new; local hosts = hosts; local prosody = prosody; local pcall, xpcall = pcall, xpcall; local setmetatable, rawget = setmetatable, rawget; local ipairs, pairs, type, tostring, t_insert = ipairs, pairs, type, tostring, table.insert; local debug_traceback = debug.traceback; local unpack, select = unpack, select; pcall = function(f, ...) local n = select("#", ...); local params = {...}; return xpcall(function() return f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end); end local autoload_modules = {"presence", "message", "iq", "offline", "c2s", "s2s"}; local component_inheritable_modules = {"tls", "dialback", "iq", "s2s"}; -- We need this to let modules access the real global namespace local _G = _G; module "modulemanager" local api = _G.require "core.moduleapi"; -- Module API container -- [host] = { [module] = module_env } local modulemap = { ["*"] = {} }; -- Load modules when a host is activated function load_modules_for_host(host) local component = config.get(host, "component_module"); local global_modules_enabled = config.get("*", "modules_enabled"); local global_modules_disabled = config.get("*", "modules_disabled"); local host_modules_enabled = config.get(host, "modules_enabled"); local host_modules_disabled = config.get(host, "modules_disabled"); if host_modules_enabled == global_modules_enabled then host_modules_enabled = nil; end if host_modules_disabled == global_modules_disabled then host_modules_disabled = nil; end local global_modules = set.new(autoload_modules) + set.new(global_modules_enabled) - set.new(global_modules_disabled); if component then global_modules = set.intersection(set.new(component_inheritable_modules), global_modules); end local modules = (global_modules + set.new(host_modules_enabled)) - set.new(host_modules_disabled); -- COMPAT w/ pre 0.8 if modules:contains("console") then log("error", "The mod_console plugin has been renamed to mod_admin_telnet. Please update your config."); modules:remove("console"); modules:add("admin_telnet"); end if component then load(host, component); end for module in modules do load(host, module); end end prosody.events.add_handler("host-activated", load_modules_for_host); prosody.events.add_handler("host-deactivated", function (host) modulemap[host] = nil; end); --- Private helpers --- local function do_unload_module(host, name) local mod = get_module(host, name); if not mod then return nil, "module-not-loaded"; end if module_has_method(mod, "unload") then local ok, err = call_module_method(mod, "unload"); if (not ok) and err then log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err); end end for object, event, handler in mod.module.event_handlers:iter(nil, nil, nil) do object.remove_handler(event, handler); end if mod.module.items then -- remove items local events = (host == "*" and prosody.events) or hosts[host].events; for key,t in pairs(mod.module.items) do for i = #t,1,-1 do local value = t[i]; t[i] = nil; events.fire_event("item-removed/"..key, {source = mod.module, item = value}); end end end mod.module.loaded = false; modulemap[host][name] = nil; return true; end local function do_load_module(host, module_name, state) if not (host and module_name) then return nil, "insufficient-parameters"; elseif not hosts[host] and host ~= "*"then return nil, "unknown-host"; end if not modulemap[host] then modulemap[host] = hosts[host].modules; end if modulemap[host][module_name] then log("warn", "%s is already loaded for %s, so not loading again", module_name, host); return nil, "module-already-loaded"; elseif modulemap["*"][module_name] then local mod = modulemap["*"][module_name]; if module_has_method(mod, "add_host") then local _log = logger.init(host..":"..module_name); local host_module_api = setmetatable({ host = host, event_handlers = new_multitable(), items = {}; _log = _log, log = function (self, ...) return _log(...); end; },{ __index = modulemap["*"][module_name].module; }); local host_module = setmetatable({ module = host_module_api }, { __index = mod }); host_module_api.environment = host_module; modulemap[host][module_name] = host_module; local ok, result, module_err = call_module_method(mod, "add_host", host_module_api); if not ok or result == false then modulemap[host][module_name] = nil; return nil, ok and module_err or result; end return host_module; end return nil, "global-module-already-loaded"; end local _log = logger.init(host..":"..module_name); local api_instance = setmetatable({ name = module_name, host = host, _log = _log, log = function (self, ...) return _log(...); end, event_handlers = new_multitable(), reloading = not not state, saved_state = state~=true and state or nil } , { __index = api }); local pluginenv = setmetatable({ module = api_instance }, { __index = _G }); api_instance.environment = pluginenv; local mod, err = pluginloader.load_code(module_name, nil, pluginenv); if not mod then log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil"); return nil, err; end api_instance.path = err; modulemap[host][module_name] = pluginenv; local ok, err = pcall(mod); if ok then -- Call module's "load" if module_has_method(pluginenv, "load") then ok, err = call_module_method(pluginenv, "load"); if not ok then log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil"); end end api_instance.reloading, api_instance.saved_state = nil, nil; if api_instance.host == "*" then if not api_instance.global then -- COMPAT w/pre-0.9 if host ~= "*" then log("warn", "mod_%s: Setting module.host = '*' deprecated, call module:set_global() instead", module_name); end api_instance:set_global(); end modulemap[host][module_name] = nil; modulemap[api_instance.host][module_name] = pluginenv; if host ~= api_instance.host and module_has_method(pluginenv, "add_host") then -- Now load the module again onto the host it was originally being loaded on ok, err = do_load_module(host, module_name); end end end if not ok then modulemap[api_instance.host][module_name] = nil; log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil"); end return ok and pluginenv, err; end local function do_reload_module(host, name) local mod = get_module(host, name); if not mod then return nil, "module-not-loaded"; end local _mod, err = pluginloader.load_code(name); -- checking for syntax errors if not _mod then log("error", "Unable to load module '%s': %s", name or "nil", err or "nil"); return nil, err; end local saved; if module_has_method(mod, "save") then local ok, ret, err = call_module_method(mod, "save"); if ok then saved = ret; else log("warn", "Error saving module '%s:%s' state: %s", host, name, ret); if not config.get(host, "force_module_reload") then log("warn", "Aborting reload due to error, set force_module_reload to ignore this"); return nil, "save-state-failed"; else log("warn", "Continuing with reload (using the force)"); end end end mod.module.reloading = true; do_unload_module(host, name); local ok, err = do_load_module(host, name, saved or true); if ok then mod = get_module(host, name); if module_has_method(mod, "restore") then local ok, err = call_module_method(mod, "restore", saved or {}) if (not ok) and err then log("warn", "Error restoring module '%s' from '%s': %s", name, host, err); end end end return ok and mod, err; end --- Public API --- -- Load a module and fire module-loaded event function load(host, name) local mod, err = do_load_module(host, name); if mod then (hosts[mod.module.host] or prosody).events.fire_event("module-loaded", { module = name, host = mod.module.host }); end return mod, err; end -- Unload a module and fire module-unloaded function unload(host, name) local ok, err = do_unload_module(host, name); if ok then (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host }); end return ok, err; end function reload(host, name) local mod, err = do_reload_module(host, name); if mod then modulemap[host][name].module.reloading = true; (hosts[host] or prosody).events.fire_event("module-reloaded", { module = name, host = host }); mod.module.reloading = nil; elseif not is_loaded(host, name) then (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host }); end return mod, err; end function get_module(host, name) return modulemap[host] and modulemap[host][name]; end function get_items(key, host) local result = {}; local modules = modulemap[host]; if not key or not host or not modules then return nil; end for _, module in pairs(modules) do local mod = module.module; if mod.items and mod.items[key] then for _, value in ipairs(mod.items[key]) do t_insert(result, value); end end end return result; end function get_modules(host) return modulemap[host]; end function is_loaded(host, name) return modulemap[host] and modulemap[host][name] and true; end function module_has_method(module, method) return type(rawget(module.module, method)) == "function"; end function call_module_method(module, method, ...) local f = rawget(module.module, method); if type(f) == "function" then return pcall(f, ...); else return false, "no-such-method"; end end return _M; prosody-0.9.1/core/portmanager.lua0000644000175000017500000002053112213321667017102 0ustar matthewmatthewlocal config = require "core.configmanager"; local certmanager = require "core.certmanager"; local server = require "net.server"; local socket = require "socket"; local log = require "util.logger".init("portmanager"); local multitable = require "util.multitable"; local set = require "util.set"; local table = table; local setmetatable, rawset, rawget = setmetatable, rawset, rawget; local type, tonumber, tostring, ipairs, pairs = type, tonumber, tostring, ipairs, pairs; local prosody = prosody; local fire_event = prosody.events.fire_event; module "portmanager"; --- Config local default_interfaces = { }; local default_local_interfaces = { }; if config.get("*", "use_ipv4") ~= false then table.insert(default_interfaces, "*"); table.insert(default_local_interfaces, "127.0.0.1"); end if socket.tcp6 and config.get("*", "use_ipv6") ~= false then table.insert(default_interfaces, "::"); table.insert(default_local_interfaces, "::1"); end --- Private state -- service_name -> { service_info, ... } local services = setmetatable({}, { __index = function (t, k) rawset(t, k, {}); return rawget(t, k); end }); -- service_name, interface (string), port (number) local active_services = multitable.new(); --- Private helpers local function error_to_friendly_message(service_name, port, err) local friendly_message = err; if err:match(" in use") then -- FIXME: Use service_name here if port == 5222 or port == 5223 or port == 5269 then friendly_message = "check that Prosody or another XMPP server is " .."not already running and using this port"; elseif port == 80 or port == 81 then friendly_message = "check that a HTTP server is not already using " .."this port"; elseif port == 5280 then friendly_message = "check that Prosody or a BOSH connection manager " .."is not already running"; else friendly_message = "this port is in use by another application"; end elseif err:match("permission") then friendly_message = "Prosody does not have sufficient privileges to use this port"; end return friendly_message; end prosody.events.add_handler("item-added/net-provider", function (event) local item = event.item; register_service(item.name, item); end); prosody.events.add_handler("item-removed/net-provider", function (event) local item = event.item; unregister_service(item.name, item); end); local function duplicate_ssl_config(ssl_config) local ssl_config = type(ssl_config) == "table" and ssl_config or {}; local _config = {}; for k, v in pairs(ssl_config) do _config[k] = v; end return _config; end --- Public API function activate(service_name) local service_info = services[service_name][1]; if not service_info then return nil, "Unknown service: "..service_name; end local listener = service_info.listener; local config_prefix = (service_info.config_prefix or service_name).."_"; if config_prefix == "_" then config_prefix = ""; end local bind_interfaces = config.get("*", config_prefix.."interfaces") or config.get("*", config_prefix.."interface") -- COMPAT w/pre-0.9 or (service_info.private and (config.get("*", "local_interfaces") or default_local_interfaces)) or config.get("*", "interfaces") or config.get("*", "interface") -- COMPAT w/pre-0.9 or listener.default_interface -- COMPAT w/pre0.9 or default_interfaces bind_interfaces = set.new(type(bind_interfaces)~="table" and {bind_interfaces} or bind_interfaces); local bind_ports = config.get("*", config_prefix.."ports") or service_info.default_ports or {service_info.default_port or listener.default_port -- COMPAT w/pre-0.9 } bind_ports = set.new(type(bind_ports) ~= "table" and { bind_ports } or bind_ports ); local mode, ssl = listener.default_mode or "*a"; local hooked_ports = {}; for interface in bind_interfaces do for port in bind_ports do local port_number = tonumber(port); if not port_number then log("error", "Invalid port number specified for service '%s': %s", service_info.name, tostring(port)); elseif #active_services:search(nil, interface, port_number) > 0 then log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port, active_services:search(nil, interface, port)[1][1].service.name or "", service_name or ""); else local err; -- Create SSL context for this service/port if service_info.encryption == "ssl" then local ssl_config = duplicate_ssl_config((config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[interface]) or (config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[port]) or config.get("*", config_prefix.."ssl") or (config.get("*", "ssl") and config.get("*", "ssl")[interface]) or (config.get("*", "ssl") and config.get("*", "ssl")[port]) or config.get("*", "ssl")); -- add default entries for, or override ssl configuration if ssl_config and service_info.ssl_config then for key, value in pairs(service_info.ssl_config) do if not service_info.ssl_config_override and not ssl_config[key] then ssl_config[key] = value; elseif service_info.ssl_config_override then ssl_config[key] = value; end end end ssl, err = certmanager.create_context(service_info.name.." port "..port, "server", ssl_config); if not ssl then log("error", "Error binding encrypted port for %s: %s", service_info.name, error_to_friendly_message(service_name, port_number, err) or "unknown error"); end end if not err then -- Start listening on interface+port local handler, err = server.addserver(interface, port_number, listener, mode, ssl); if not handler then log("error", "Failed to open server port %d on %s, %s", port_number, interface, error_to_friendly_message(service_name, port_number, err)); else table.insert(hooked_ports, "["..interface.."]:"..port_number); log("debug", "Added listening service %s to [%s]:%d", service_name, interface, port_number); active_services:add(service_name, interface, port_number, { server = handler; service = service_info; }); end end end end end log("info", "Activated service '%s' on %s", service_name, #hooked_ports == 0 and "no ports" or table.concat(hooked_ports, ", ")); return true; end function deactivate(service_name, service_info) for name, interface, port, n, active_service in active_services:iter(service_name or service_info and service_info.name, nil, nil, nil) do if service_info == nil or active_service.service == service_info then close(interface, port); end end log("info", "Deactivated service '%s'", service_name or service_info.name); end function register_service(service_name, service_info) table.insert(services[service_name], service_info); if not active_services:get(service_name) then log("debug", "No active service for %s, activating...", service_name); local ok, err = activate(service_name); if not ok then log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error"); end end fire_event("service-added", { name = service_name, service = service_info }); return true; end function unregister_service(service_name, service_info) log("debug", "Unregistering service: %s", service_name); local service_info_list = services[service_name]; for i, service in ipairs(service_info_list) do if service == service_info then table.remove(service_info_list, i); end end deactivate(nil, service_info); if #service_info_list > 0 then -- Other services registered with this name activate(service_name); -- Re-activate with the next available one end fire_event("service-removed", { name = service_name, service = service_info }); end function close(interface, port) local service, server = get_service_at(interface, port); if not service then return false, "port-not-open"; end server:close(); active_services:remove(service.name, interface, port); log("debug", "Removed listening service %s from [%s]:%d", service.name, interface, port); return true; end function get_service_at(interface, port) local data = active_services:search(nil, interface, port)[1][1]; return data.service, data.server; end function get_service(service_name) return (services[service_name] or {})[1]; end function get_active_services(...) return active_services; end function get_registered_services() return services; end return _M; prosody-0.9.1/core/configmanager.lua0000644000175000017500000001652512213321667017373 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local _G = _G; local setmetatable, rawget, rawset, io, error, dofile, type, pairs, table = setmetatable, rawget, rawset, io, error, dofile, type, pairs, table; 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 path_sep = package.config:sub(1,1); module "configmanager" local parsers = {}; local config_mt = { __index = function (t, k) return rawget(t, "*"); end}; local config = setmetatable({ ["*"] = { } }, config_mt); -- When host not found, use global local host_mt = { __index = function(_, k) return config["*"][k] end } function getconfig() return config; end function get(host, key, _oldkey) if key == "core" then key = _oldkey; -- COMPAT with code that still uses "core" end return config[host][key]; end function _M.rawget(host, key, _oldkey) if key == "core" then key = _oldkey; -- COMPAT with code that still uses "core" end local hostconfig = rawget(config, host); if hostconfig then return rawget(hostconfig, key); end end local function set(config, host, key, value) if host and key then local hostconfig = rawget(config, host); if not hostconfig then hostconfig = rawset(config, host, setmetatable({}, host_mt))[host]; end hostconfig[key] = value; return true; end return false; end function _M.set(host, key, value, _oldvalue) if key == "core" then key, value = value, _oldvalue; --COMPAT with code that still uses "core" end 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+$"); if parsers[format] and parsers[format].load then local f, err = io.open(filename); if f then local new_config = setmetatable({ ["*"] = { } }, config_mt); local ok, err = parsers[format].load(f:read("*a"), filename, new_config); f:close(); if ok then config = new_config; fire_event("config-reloaded", { filename = filename, format = format, config = config }); end return ok, "parser", err; end return f, "file", err; end if not format then return nil, "file", "no parser specified"; else return nil, "file", "no parser for "..(format); end end function save(filename, format) end function addparser(format, parser) if format and parser then parsers[format] = parser; end end -- _M needed to avoid name clash with local 'parsers' function _M.parsers() local p = {}; for format in pairs(parsers) do table.insert(p, format); end return p; end -- Built-in Lua parser do local pcall, setmetatable = _G.pcall, _G.setmetatable; local rawget = _G.rawget; parsers.lua = {}; function parsers.lua.load(data, config_file, config) local env; -- The ' = true' are needed so as not to set off __newindex when we assign the functions below env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true, Include = true, include = true, RunScript = true }, { __index = function (t, k) return rawget(_G, k); end, __newindex = function (t, k, v) set(config, env.__currenthost or "*", k, v); end }); rawset(env, "__currenthost", "*") -- Default is global function env.VirtualHost(name) if rawget(config, name) and rawget(config[name], "component_module") then error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s", name, config[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0); end rawset(env, "__currenthost", name); -- Needs at least one setting to logically exist :) set(config, name or "*", "defined", true); return function (config_options) rawset(env, "__currenthost", "*"); -- Return to global scope for option_name, option_value in pairs(config_options) do set(config, name or "*", option_name, option_value); end end; end env.Host, env.host = env.VirtualHost, env.VirtualHost; function env.Component(name) if rawget(config, name) and rawget(config[name], "defined") and not rawget(config[name], "component_module") then error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s", name, name, name), 0); end set(config, name, "component_module", "component"); -- Don't load the global modules by default set(config, name, "load_global_modules", false); rawset(env, "__currenthost", name); local function handle_config_options(config_options) rawset(env, "__currenthost", "*"); -- Return to global scope for option_name, option_value in pairs(config_options) do set(config, name or "*", option_name, option_value); end end return function (module) if type(module) == "string" then set(config, name, "component_module", module); return handle_config_options; end return handle_config_options(module); end end env.component = env.Component; function env.Include(file) if file:match("[*?]") then 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.."]+$", ""); if #path > 0 then path = resolve_relative_path(config_path, path); else path = config_path; end local patt = glob_to_pattern(glob); for f in lfs.dir(path) do if f:sub(1,1) ~= "." and f:match(patt) then env.Include(path..path_sep..f); end end else local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file); local f, err = io.open(file); if f then local ret, err = parsers.lua.load(f:read("*a"), file, config); if not ret then error(err:gsub("%[string.-%]", file), 0); end end if not f then error("Error loading included "..file..": "..err, 0); end return f, err; end end env.include = env.Include; function env.RunScript(file) return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file)); end local chunk, err = envload(data, "@"..config_file, env); if not chunk then return nil, err; end local ok, err = pcall(chunk); if not ok then return nil, err; end return true; end end return _M; prosody-0.9.1/core/moduleapi.lua0000644000175000017500000002476712213321667016561 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2012 Matthew Wild -- Copyright (C) 2008-2012 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; local modulemanager = require "modulemanager"; -- This is necessary to avoid require loops local array = require "util.array"; local set = require "util.set"; local logger = require "util.logger"; local pluginloader = require "util.pluginloader"; local timer = require "util.timer"; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack; local tonumber, tostring = tonumber, tostring; local prosody = prosody; local hosts = prosody.hosts; -- 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" }); local NULL = {}; local api = {}; -- Returns the name of the current module function api:get_name() return self.name; end -- Returns the host that the current module is serving function api:get_host() return self.host; end function api:get_host_type() return self.host ~= "*" and hosts[self.host].type or nil; end function api:set_global() self.host = "*"; -- Update the logger local _log = logger.init("mod_"..self.name); self.log = function (self, ...) return _log(...); end; self._log = _log; self.global = true; end function api:add_feature(xmlns) self:add_item("feature", xmlns); end function api:add_identity(category, type, name) self:add_item("identity", {category = category, type = type, name = name}); end function api:add_extension(data) self:add_item("extension", data); end function api:has_feature(xmlns) for _, feature in ipairs(self:get_host_items("feature")) do if feature == xmlns then return true; end end return false; end function api:has_identity(category, type, name) for _, id in ipairs(self:get_host_items("identity")) do if id.category == category and id.type == type and id.name == name then return true; end end return false; end function api:fire_event(...) return (hosts[self.host] or prosody).events.fire_event(...); end function api:hook_object_event(object, event, handler, priority) self.event_handlers:set(object, event, handler, true); return object.add_handler(event, handler, priority); end function api:unhook_object_event(object, event, handler) return object.remove_handler(event, handler); end function api:hook(event, handler, priority) return self:hook_object_event((hosts[self.host] or prosody).events, event, handler, priority); end function api:hook_global(event, handler, priority) return self:hook_object_event(prosody.events, event, handler, priority); end function api:hook_tag(xmlns, name, handler, priority) if not handler and type(name) == "function" then -- If only 2 options then they specified no xmlns xmlns, name, handler, priority = nil, xmlns, name, handler; elseif not (handler and name) then self:log("warn", "Error: Insufficient parameters to module:hook_stanza()"); return; end return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority); end api.hook_stanza = api.hook_tag; -- COMPAT w/pre-0.9 function api:require(lib) local f, n = pluginloader.load_code(self.name, lib..".lib.lua", self.environment); if not f then f, n = pluginloader.load_code(lib, lib..".lib.lua", self.environment); end if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message return f(); end function api:depends(name) if not self.dependencies then self.dependencies = {}; self:hook("module-reloaded", function (event) if self.dependencies[event.module] and not self.reloading then self:log("info", "Auto-reloading due to reload of %s:%s", event.host, event.module); modulemanager.reload(self.host, self.name); return; end end); self:hook("module-unloaded", function (event) if self.dependencies[event.module] then self:log("info", "Auto-unloading due to unload of %s:%s", event.host, event.module); modulemanager.unload(self.host, self.name); end end); end local mod = modulemanager.get_module(self.host, name) or modulemanager.get_module("*", name); if mod and mod.module.host == "*" and self.host ~= "*" and modulemanager.module_has_method(mod, "add_host") then mod = nil; -- Target is a shared module, so we still want to load it on our host end if not mod then local err; mod, err = modulemanager.load(self.host, name); if not mod then return error(("Unable to load required module, mod_%s: %s"):format(name, ((err or "unknown error"):gsub("%-", " ")) )); end end self.dependencies[name] = true; return mod; end -- Returns one or more shared tables at the specified virtual paths -- Intentionally does not allow the table at a path to be _set_, it -- is auto-created if it does not exist. function api:shared(...) if not self.shared_data then self.shared_data = {}; end local paths = { n = select("#", ...), ... }; local data_array = {}; local default_path_components = { self.host, self.name }; for i = 1, paths.n do local path = paths[i]; if path:sub(1,1) ~= "/" then -- Prepend default components local n_components = select(2, path:gsub("/", "%1")); path = (n_components<#default_path_components and "/" or "")..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path; end local shared = shared_data[path]; if not shared then shared = {}; if path:match("%-cache$") then setmetatable(shared, { __mode = "kv" }); end shared_data[path] = shared; end t_insert(data_array, shared); self.shared_data[path] = shared; end return unpack(data_array); end function api:get_option(name, default_value) local value = config.get(self.host, name); if value == nil then value = default_value; end return value; end function api:get_option_string(name, default_value) local value = self:get_option(name, default_value); if type(value) == "table" then if #value > 1 then self:log("error", "Config option '%s' does not take a list, using just the first item", name); end value = value[1]; end if value == nil then return nil; end return tostring(value); end function api:get_option_number(name, ...) local value = self:get_option(name, ...); if type(value) == "table" then if #value > 1 then self:log("error", "Config option '%s' does not take a list, using just the first item", name); end value = value[1]; end local ret = tonumber(value); if value ~= nil and ret == nil then self:log("error", "Config option '%s' not understood, expecting a number", name); end return ret; end function api:get_option_boolean(name, ...) local value = self:get_option(name, ...); if type(value) == "table" then if #value > 1 then self:log("error", "Config option '%s' does not take a list, using just the first item", name); end value = value[1]; end if value == nil then return nil; end local ret = value == true or value == "true" or value == 1 or nil; if ret == nil then ret = (value == false or value == "false" or value == 0); if ret then ret = false; else ret = nil; end end if ret == nil then self:log("error", "Config option '%s' not understood, expecting true/false", name); end return ret; end function api:get_option_array(name, ...) local value = self:get_option(name, ...); if value == nil then return nil; end if type(value) ~= "table" then return array{ value }; -- Assume any non-list is a single-item list end return array():append(value); -- Clone end function api:get_option_set(name, ...) local value = self:get_option_array(name, ...); if value == nil then return nil; end 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 function api:add_item(key, value) self.items = self.items or {}; self.items[key] = self.items[key] or {}; t_insert(self.items[key], value); self:fire_event("item-added/"..key, {source = self, item = value}); end function api:remove_item(key, value) local t = self.items and self.items[key] or NULL; for i = #t,1,-1 do if t[i] == value then t_remove(self.items[key], i); self:fire_event("item-removed/"..key, {source = self, item = value}); return value; end end end function api:get_host_items(key) local result = modulemanager.get_items(key, self.host) or {}; return result; end function api:handle_items(type, added_cb, removed_cb, existing) self:hook("item-added/"..type, added_cb); self:hook("item-removed/"..type, removed_cb); if existing ~= false then for _, item in ipairs(self:get_host_items(type)) do added_cb({ item = item }); end end end function api:provides(name, item) -- 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 -- (e.g. "auth_foo" -> "foo" for an auth provider) if item_name:find(name.."_", 1, true) == 1 then item_name = item_name:sub(#name+2); end item.name = item_name; end item._provided_by = self.name; self:add_item(name.."-provider", item); end function api:send(stanza) return core_post_stanza(hosts[self.host], stanza); end function api:add_timer(delay, callback) return timer.add_task(delay, function (t) if self.loaded == false then return; end return callback(t); end); end local path_sep = package.config:sub(1,1); function api:get_directory() return self.path and (self.path:gsub("%"..path_sep.."[^"..path_sep.."]*$", "")) or nil; end function api:load_resource(path, mode) path = config.resolve_relative_path(self:get_directory(), path); return io.open(path, mode); end function api:open_store(name, type) return storagemanager.open(self.host, name or self.name, type); end return api; prosody-0.9.1/core/usermanager.lua0000644000175000017500000001117512213321667017100 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local modulemanager = require "core.modulemanager"; local log = require "util.logger".init("usermanager"); local type = type; local ipairs = ipairs; local pairs = pairs; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local config = require "core.configmanager"; local hosts = hosts; local sasl_new = require "util.sasl".new; local storagemanager = require "core.storagemanager"; local prosody = _G.prosody; local setmetatable = setmetatable; local default_provider = "internal_plain"; module "usermanager" function new_null_provider() local function dummy() return nil, "method not implemented"; end; local function dummy_get_sasl_handler() return sasl_new(nil, {}); end return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, { __index = function(self, method) return dummy; end }); end local provider_mt = { __index = new_null_provider() }; function initialize_host(host) local host_session = hosts[host]; if host_session.type ~= "local" then return; end host_session.events.add_handler("item-added/auth-provider", function (event) local provider = event.item; local auth_provider = config.get(host, "authentication") or default_provider; if config.get(host, "anonymous_login") then log("error", "Deprecated config option 'anonymous_login'. Use authentication = 'anonymous' instead."); auth_provider = "anonymous"; end -- COMPAT 0.7 if provider.name == auth_provider then host_session.users = setmetatable(provider, provider_mt); end if host_session.users ~= nil and host_session.users.name ~= nil then log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name); end end); host_session.events.add_handler("item-removed/auth-provider", function (event) local provider = event.item; if host_session.users == provider then host_session.users = new_null_provider(); end end); host_session.users = new_null_provider(); -- Start with the default usermanager provider local auth_provider = config.get(host, "authentication") or default_provider; if config.get(host, "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7 if auth_provider ~= "null" then modulemanager.load(host, "auth_"..auth_provider); end end; prosody.events.add_handler("host-activated", initialize_host, 100); function test_password(username, host, password) return hosts[host].users.test_password(username, password); end function get_password(username, host) return hosts[host].users.get_password(username); end function set_password(username, password, host) return hosts[host].users.set_password(username, password); end function user_exists(username, host) return hosts[host].users.user_exists(username); end function create_user(username, password, host) return hosts[host].users.create_user(username, password); end function delete_user(username, host) local ok, err = hosts[host].users.delete_user(username); if not ok then return nil, err; end prosody.events.fire_event("user-deleted", { username = username, host = host }); return storagemanager.purge(username, host); end function users(host) return hosts[host].users.users(); end function get_sasl_handler(host, session) return hosts[host].users.get_sasl_handler(session); end function get_provider(host) return hosts[host].users; end function is_admin(jid, host) if host and not hosts[host] then return false; end if type(jid) ~= "string" then return false; end local is_admin; jid = jid_bare(jid); host = host or "*"; local host_admins = config.get(host, "admins"); local global_admins = config.get("*", "admins"); if host_admins and host_admins ~= global_admins then if type(host_admins) == "table" then for _,admin in ipairs(host_admins) do if jid_prep(admin) == jid then is_admin = true; break; end end elseif host_admins then log("error", "Option 'admins' for host '%s' is not a list", host); end end if not is_admin and global_admins then if type(global_admins) == "table" then for _,admin in ipairs(global_admins) do if jid_prep(admin) == jid then is_admin = true; break; end end elseif global_admins then log("error", "Global option 'admins' is not a list"); end end -- Still not an admin, check with auth provider if not is_admin and host ~= "*" and hosts[host].users and hosts[host].users.is_admin then is_admin = hosts[host].users.is_admin(jid); end return is_admin or false; end return _M; prosody-0.9.1/core/hostmanager.lua0000644000175000017500000001214512213321667017075 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local configmanager = require "core.configmanager"; local modulemanager = require "core.modulemanager"; local events_new = require "util.events".new; local disco_items = require "util.multitable".new(); local NULL = {}; local jid_split = require "util.jid".split; local uuid_gen = require "util.uuid".generate; local log = require "util.logger".init("hostmanager"); local hosts = prosody.hosts; local prosody_events = prosody.events; if not _G.prosody.incoming_s2s then require "core.s2smanager"; end local incoming_s2s = _G.prosody.incoming_s2s; local core_route_stanza = _G.prosody.core_route_stanza; local pairs, select, rawget = pairs, select, rawget; local tostring, type = tostring, type; module "hostmanager" local hosts_loaded_once; local function load_enabled_hosts(config) local defined_hosts = config or configmanager.getconfig(); local activated_any_host; for host, host_config in pairs(defined_hosts) do if host ~= "*" and host_config.enabled ~= false then if not host_config.component_module then activated_any_host = true; end activate(host, host_config); end end if not activated_any_host then log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded."); end prosody_events.fire_event("hosts-activated", defined_hosts); hosts_loaded_once = true; end prosody_events.add_handler("server-starting", load_enabled_hosts); local function host_send(stanza) local name, type = stanza.name, stanza.attr.type; if type == "error" or (name == "iq" and type == "result") then local dest_host_name = select(2, jid_split(stanza.attr.to)); local dest_host = hosts[dest_host_name] or { type = "unknown" }; log("warn", "Unhandled response sent to %s host %s: %s", dest_host.type, dest_host_name, tostring(stanza)); return; end core_route_stanza(nil, stanza); end function activate(host, host_config) if rawget(hosts, host) then return nil, "The host "..host.." is already activated"; end host_config = host_config or configmanager.getconfig()[host]; if not host_config then return nil, "Couldn't find the host "..tostring(host).." defined in the current config"; end local host_session = { host = host; s2sout = {}; events = events_new(); dialback_secret = configmanager.get(host, "dialback_secret") or uuid_gen(); send = host_send; modules = {}; }; if not host_config.component_module then -- host host_session.type = "local"; host_session.sessions = {}; else -- component host_session.type = "component"; end hosts[host] = host_session; if not host:match("[@/]") then disco_items:set(host:match("%.(.*)") or "*", host, host_config.name or true); end for option_name in pairs(host_config) do if option_name:match("_ports$") or option_name:match("_interface$") then log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name); end end log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host); prosody_events.fire_event("host-activated", host); return true; end function deactivate(host, reason) local host_session = hosts[host]; if not host_session then return nil, "The host "..tostring(host).." is not activated"; end log("info", "Deactivating host: %s", host); prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason }); if type(reason) ~= "table" then reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) }; end -- Disconnect local users, s2s connections -- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?) if host_session.sessions then for username, user in pairs(host_session.sessions) do for resource, session in pairs(user.sessions) do log("debug", "Closing connection for %s@%s/%s", username, host, resource); session:close(reason); end end end if host_session.s2sout then for remotehost, session in pairs(host_session.s2sout) do if session.close then log("debug", "Closing outgoing connection to %s", remotehost); if session.srv_hosts then session.srv_hosts = nil; end session:close(reason); end end end for remote_session in pairs(incoming_s2s) do if remote_session.to_host == host then log("debug", "Closing incoming connection from %s", remote_session.from_host or ""); remote_session:close(reason); end end -- TODO: This should be done in modulemanager if host_session.modules then for module in pairs(host_session.modules) do modulemanager.unload(host, module); end end hosts[host] = nil; if not host:match("[@/]") then disco_items:remove(host:match("%.(.*)") or "*", host); end prosody_events.fire_event("host-deactivated", host); log("info", "Deactivated host: %s", host); return true; end function get_children(host) return disco_items:get(host) or NULL; end return _M; prosody-0.9.1/core/rostermanager.lua0000644000175000017500000002343212213321667017437 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = require "util.logger".init("rostermanager"); local pairs = pairs; local tostring = tostring; local hosts = hosts; local bare_sessions = bare_sessions; local datamanager = require "util.datamanager" local um_user_exists = require "core.usermanager".user_exists; local st = require "util.stanza"; module "rostermanager" function add_to_roster(session, jid, item) if session.roster then local old_item = session.roster[jid]; session.roster[jid] = item; if save_roster(session.username, session.host) then return true; else session.roster[jid] = old_item; return nil, "wait", "internal-server-error", "Unable to save roster"; end else return nil, "auth", "not-authorized", "Session's roster not loaded"; end end function remove_from_roster(session, jid) if session.roster then local old_item = session.roster[jid]; session.roster[jid] = nil; if save_roster(session.username, session.host) then return true; else session.roster[jid] = old_item; return nil, "wait", "internal-server-error", "Unable to save roster"; end else return nil, "auth", "not-authorized", "Session's roster not loaded"; end end function roster_push(username, host, jid) local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; if roster then local item = hosts[host].sessions[username].roster[jid]; local stanza = st.iq({type="set"}); stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1") }); if item then stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask}); for group in pairs(item.groups) do stanza:tag("group"):text(group):up(); end else stanza:tag("item", {jid = jid, subscription = "remove"}); end stanza:up(); -- move out from item stanza:up(); -- move out from stanza -- stanza ready for _, session in pairs(hosts[host].sessions[username].sessions) do if session.interested then -- FIXME do we need to set stanza.attr.to? session.send(stanza); end end end end function load_roster(username, host) local jid = username.."@"..host; log("debug", "load_roster: asked for: %s", jid); local user = bare_sessions[jid]; local roster; if user then roster = user.roster; if roster then return roster; end log("debug", "load_roster: loading for new user: %s@%s", username, host); else -- Attempt to load roster for non-loaded user log("debug", "load_roster: loading for offline user: %s@%s", username, host); end local data, err = datamanager.load(username, host, "roster"); roster = data or {}; if user then user.roster = roster; end if not roster[false] then roster[false] = { broken = err or nil }; end if roster[jid] then roster[jid] = nil; log("warn", "roster for %s has a self-contact", jid); end if not err then hosts[host].events.fire_event("roster-load", username, host, roster); end return roster, err; end function save_roster(username, host, roster) if not um_user_exists(username, host) then log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host); return nil; end log("debug", "save_roster: saving roster for %s@%s", username, host); if not roster then roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; --if not roster then -- --roster = load_roster(username, host); -- return true; -- roster unchanged, no reason to save --end end if roster then local metadata = roster[false]; if not metadata then metadata = {}; roster[false] = metadata; end if metadata.version ~= true then metadata.version = (metadata.version or 0) + 1; end if roster[false].broken then return nil, "Not saving broken roster" end return datamanager.store(username, host, "roster", roster); end log("warn", "save_roster: user had no roster to save"); return nil; end function process_inbound_subscription_approval(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and item.ask then if item.subscription == "none" then item.subscription = "to"; else -- subscription == from item.subscription = "both"; end item.ask = nil; return save_roster(username, host, roster); end end function process_inbound_subscription_cancellation(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local changed = nil; if is_contact_pending_out(username, host, jid) then item.ask = nil; changed = true; end if item then if item.subscription == "to" then item.subscription = "none"; changed = true; elseif item.subscription == "both" then item.subscription = "from"; changed = true; end end if changed then return save_roster(username, host, roster); end end function process_inbound_unsubscribe(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local changed = nil; if is_contact_pending_in(username, host, jid) then roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty? changed = true; end if item then if item.subscription == "from" then item.subscription = "none"; changed = true; elseif item.subscription == "both" then item.subscription = "to"; changed = true; end end if changed then return save_roster(username, host, roster); end end local function _get_online_roster_subscription(jidA, jidB) local user = bare_sessions[jidA]; local item = user and (user.roster[jidB] or { subscription = "none" }); return item and item.subscription; end function is_contact_subscribed(username, host, jid) do local selfjid = username.."@"..host; local subscription = _get_online_roster_subscription(selfjid, jid); if subscription then return (subscription == "both" or subscription == "from"); end local subscription = _get_online_roster_subscription(jid, selfjid); if subscription then return (subscription == "both" or subscription == "to"); end end local roster, err = load_roster(username, host); local item = roster[jid]; return item and (item.subscription == "from" or item.subscription == "both"), err; end function is_contact_pending_in(username, host, jid) local roster = load_roster(username, host); return roster.pending and roster.pending[jid]; end function set_contact_pending_in(username, host, jid, pending) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- false end if not roster.pending then roster.pending = {}; end roster.pending[jid] = true; return save_roster(username, host, roster); end function is_contact_pending_out(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; return item and item.ask; end function set_contact_pending_out(username, host, jid) -- subscribe local roster = load_roster(username, host); local item = roster[jid]; if item and (item.ask or item.subscription == "to" or item.subscription == "both") then return true; end if not item then item = {subscription = "none", groups = {}}; roster[jid] = item; end item.ask = "subscribe"; log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid); return save_roster(username, host, roster); end function unsubscribe(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if not item then return false; end if (item.subscription == "from" or item.subscription == "none") and not item.ask then return true; end item.ask = nil; if item.subscription == "both" then item.subscription = "from"; elseif item.subscription == "to" then item.subscription = "none"; end return save_roster(username, host, roster); end function subscribed(username, host, jid) if is_contact_pending_in(username, host, jid) then local roster = load_roster(username, host); local item = roster[jid]; if not item then -- FIXME should roster item be auto-created? item = {subscription = "none", groups = {}}; roster[jid] = item; end if item.subscription == "none" then item.subscription = "from"; else -- subscription == to item.subscription = "both"; end roster.pending[jid] = nil; -- TODO maybe remove roster.pending if empty return save_roster(username, host, roster); end -- TODO else implement optional feature pre-approval (ask = subscribed) end function unsubscribed(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; local pending = is_contact_pending_in(username, host, jid); if pending then roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty? end local subscribed; if item then if item.subscription == "from" then item.subscription = "none"; subscribed = true; elseif item.subscription == "both" then item.subscription = "to"; subscribed = true; end end local success = (pending or subscribed) and save_roster(username, host, roster); return success, pending, subscribed; end function process_outbound_subscription_request(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "none" or item.subscription == "from") then item.ask = "subscribe"; return save_roster(username, host, roster); end end --[[function process_outbound_subscription_approval(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "none" or item.subscription == "from" then item.ask = "subscribe"; return save_roster(username, host, roster); end end]] return _M; prosody-0.9.1/core/stanza_router.lua0000644000175000017500000002154112213321667017465 0ustar matthewmatthew-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local log = require "util.logger".init("stanzarouter") local hosts = _G.prosody.hosts; local tostring = tostring; local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_prepped_split = require "util.jid".prepped_split; local full_sessions = _G.prosody.full_sessions; local bare_sessions = _G.prosody.bare_sessions; local core_post_stanza, core_process_stanza, core_route_stanza; function deprecated_warning(f) _G[f] = function(...) log("warn", "Using the global %s() is deprecated, use module:send() or prosody.%s(). %s", f, f, debug.traceback()); return prosody[f](...); end end deprecated_warning"core_post_stanza"; deprecated_warning"core_process_stanza"; deprecated_warning"core_route_stanza"; local function handle_unhandled_stanza(host, origin, stanza) local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type; if name == "iq" and xmlns == "jabber:client" then if stanza.attr.type == "get" or stanza.attr.type == "set" then xmlns = stanza.tags[1].attr.xmlns or "jabber:client"; log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns); else log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type); return true; end end if stanza.attr.xmlns == nil and origin.send then log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features log("warn", "Unhandled %s stream element or stanza: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it origin:close("unsupported-stanza-type"); end end local iq_types = { set=true, get=true, result=true, error=true }; function core_process_stanza(origin, stanza) (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag()) -- TODO verify validity of stanza (as well as JID validity) if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log if stanza.name == "iq" then if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests if not iq_types[stanza.attr.type] or ((stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1)) then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type or incorrect number of children")); return; end end if origin.type == "c2s" and not stanza.attr.xmlns then if not origin.full_jid and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind" and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then -- authenticated client isn't bound and current stanza is not a bind request if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server end return; end -- TODO also, stanzas should be returned to their original state before the function ends stanza.attr.from = origin.full_jid; end local to, xmlns = stanza.attr.to, stanza.attr.xmlns; local from = stanza.attr.from; local node, host, resource; local from_node, from_host, from_resource; local to_bare, from_bare; if to then if full_sessions[to] or bare_sessions[to] or hosts[to] then node, host = jid_split(to); -- TODO only the host is needed, optimize else node, host, resource = jid_prepped_split(to); if not host then log("warn", "Received stanza with invalid destination JID: %s", to); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The destination address is invalid: "..to)); end return; end to_bare = node and (node.."@"..host) or host; -- bare JID if resource then to = to_bare.."/"..resource; else to = to_bare; end stanza.attr.to = to; end end if from and not origin.full_jid then -- We only stamp the 'from' on c2s stanzas, so we still need to check validity from_node, from_host, from_resource = jid_prepped_split(from); if not from_host then log("warn", "Received stanza with invalid source JID: %s", from); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The source address is invalid: "..from)); end return; end from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID if from_resource then from = from_bare.."/"..from_resource; else from = from_bare; end stanza.attr.from = from; end if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then if origin.type == "s2sin" and not origin.dummy then local host_status = origin.hosts[from_host]; if not host_status or not host_status.authed then -- remote server trying to impersonate some other server? log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host); origin:close("not-authorized"); return; elseif not hosts[host] then log("warn", "Remote server %s sent us a stanza for %s, closing stream", origin.from_host, host); origin:close("host-unknown"); return; end end core_post_stanza(origin, stanza, origin.full_jid); else local h = hosts[stanza.attr.to or origin.host or origin.to_host]; if h then local event; if xmlns == nil then if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") then event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name; else event = "stanza/"..stanza.name; end else event = "stanza/"..xmlns..":"..stanza.name; end if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end end if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza); end end function core_post_stanza(origin, stanza, preevents) local to = stanza.attr.to; local node, host, resource = jid_split(to); local to_bare = node and (node.."@"..host) or host; -- bare JID local to_type, to_self; if node then if resource then to_type = '/full'; else to_type = '/bare'; if node == origin.username and host == origin.host then stanza.attr.to = nil; to_self = true; end end else if host then to_type = '/host'; else to_type = '/bare'; to_self = true; end end local event_data = {origin=origin, stanza=stanza}; if preevents then -- c2s connection if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing end local h = hosts[to_bare] or hosts[host or origin.host]; if h then if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing handle_unhandled_stanza(h.host, origin, stanza); else core_route_stanza(origin, stanza); end end function core_route_stanza(origin, stanza) local node, host, resource = jid_split(stanza.attr.to); local from_node, from_host, from_resource = jid_split(stanza.attr.from); -- Auto-detect origin if not specified origin = origin or hosts[from_host]; if not origin then return false; end if hosts[host] then -- old stanza routing code removed core_post_stanza(origin, stanza); else log("debug", "Routing to remote..."); local host_session = hosts[from_host]; if not host_session then log("error", "No hosts[from_host] (please report): %s", tostring(stanza)); else local xmlns = stanza.attr.xmlns; stanza.attr.xmlns = nil; local routed = host_session.events.fire_event("route/remote", { origin = origin, stanza = stanza, from_host = from_host, to_host = host }); stanza.attr.xmlns = xmlns; -- reset if not routed then log("debug", "... no, just kidding."); if stanza.attr.type == "error" or (stanza.name == "iq" and stanza.attr.type == "result") then return; end core_route_stanza(host_session, st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote domains is not enabled")); end end end end prosody.core_process_stanza = core_process_stanza; prosody.core_post_stanza = core_post_stanza; prosody.core_route_stanza = core_route_stanza; prosody-0.9.1/AUTHORS0000644000175000017500000000056712213321667014207 0ustar matthewmatthew The Prosody project is open to contributions (see HACKERS file), but is maintained daily by: - Matthew Wild (mail: matthew [at] prosody.im) - Waqas Hussain (mail: waqas [at] prosody.im) - Kim Alvefur (mail: zash [at] prosody.im) You can reach us collectively by email: developers [at] prosody.im or in realtime in the Prosody chatroom: prosody@conference.prosody.im prosody-0.9.1/DEPENDS0000644000175000017500000000037612213321667014142 0ustar matthewmatthew For full information on our dependencies, version requirements, and where to find them, see http://prosody.im/doc/depends If you have luarocks available on your platform, install the following: - luaexpat - luasocket - luafilesystem - luasec prosody-0.9.1/prosodyctl0000755000175000017500000006211212213321667015261 0ustar matthewmatthew#!/usr/bin/env lua -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- prosodyctl - command-line controller for Prosody XMPP server -- Will be modified by configure script if run -- CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR"); CFG_DATADIR=os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- local function is_relative(path) local path_sep = package.config:sub(1,1); return ((path_sep == "/" and path:sub(1,1) ~= "/") or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end -- Tell Lua where to find our libraries if CFG_SOURCEDIR then local function filter_relative_paths(path) if is_relative(path) then return ""; end end local function sanitise_paths(paths) return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); end package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); end -- Substitute ~ with path to home directory in data path if CFG_DATADIR then if os.getenv("HOME") then CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); end end -- Global 'prosody' object local prosody = { hosts = {}; events = require "util.events".new(); platform = "posix"; lock_globals = function () end; unlock_globals = function () end; installed = CFG_SOURCEDIR ~= nil; core_post_stanza = function () end; -- TODO: mod_router! }; _G.prosody = prosody; local dependencies = require "util.dependencies"; if not dependencies.check_dependencies() then os.exit(1); end config = require "core.configmanager" local ENV_CONFIG; do local filenames = {}; local filename; if arg[1] == "--config" and arg[2] then table.insert(filenames, arg[2]); if CFG_CONFIGDIR then table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]); end table.remove(arg, 1); table.remove(arg, 1); else for _, format in ipairs(config.parsers()) do table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format); end end for _,_filename in ipairs(filenames) do filename = _filename; local file = io.open(filename); if file then file:close(); ENV_CONFIG = filename; CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$"); break; end end local ok, level, err = config.load(filename); if not ok then print("\n"); print("**************************"); if level == "parser" then print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)"); print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err))); print(""); elseif level == "file" then print("Prosody was unable to find the configuration file."); print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist"); print("Copy or rename it to prosody.cfg.lua and edit as necessary."); end print("More help on configuring Prosody can be found at http://prosody.im/doc/configure"); print("Good luck!"); print("**************************"); print(""); os.exit(1); end end local original_logging_config = config.get("*", "log"); config.set("*", "log", { { levels = { min="info" }, to = "console" } }); local data_path = config.get("*", "data_path") or CFG_DATADIR or "data"; local custom_plugin_paths = config.get("*", "plugin_paths"); if custom_plugin_paths then local path_sep = package.config:sub(3,3); -- path1;path2;path3;defaultpath... CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); end prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, plugins = CFG_PLUGINDIR or "plugins", data = data_path }; if prosody.installed then -- Change working directory to data path. require "lfs".chdir(data_path); end require "core.loggingmanager" dependencies.log_warnings(); -- Switch away from root and into the prosody user -- local switched_user, current_uid; local want_pposix_version = "0.3.6"; local ok, pposix = pcall(require, "util.pposix"); if ok and pposix then if pposix._VERSION ~= want_pposix_version then print(string.format("Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version)); return; end current_uid = pposix.getuid(); if current_uid == 0 then -- We haz root! local desired_user = config.get("*", "prosody_user") or "prosody"; local desired_group = config.get("*", "prosody_group") or desired_user; local ok, err = pposix.setgid(desired_group); if ok then ok, err = pposix.initgroups(desired_user); end if ok then ok, err = pposix.setuid(desired_user); if ok then -- Yay! switched_user = true; end end if not switched_user then -- Boo! print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err)); end end -- Set our umask to protect data files pposix.umask(config.get("*", "umask") or "027"); pposix.setenv("HOME", data_path); pposix.setenv("PROSODY_CONFIG", ENV_CONFIG); else print("Error: Unable to load pposix module. Check that Prosody is installed correctly.") print("For more help send the below error to us through http://prosody.im/discuss"); print(tostring(pposix)) os.exit(1); end local function test_writeable(filename) local f, err = io.open(filename, "a"); if not f then return false, err; end f:close(); return true; end local unwriteable_files = {}; if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then local ok, err = test_writeable(original_logging_config); if not ok then table.insert(unwriteable_files, err); end elseif type(original_logging_config) == "table" then for _, rule in ipairs(original_logging_config) do if rule.filename then local ok, err = test_writeable(rule.filename); if not ok then table.insert(unwriteable_files, err); end end end end if #unwriteable_files > 0 then print("One of more of the Prosody log files are not"); print("writeable, please correct the errors and try"); print("starting prosodyctl again."); print(""); for _, err in ipairs(unwriteable_files) do print(err); end print(""); os.exit(1); end local error_messages = setmetatable({ ["invalid-username"] = "The given username is invalid in a Jabber ID"; ["invalid-hostname"] = "The given hostname is invalid"; ["no-password"] = "No password was supplied"; ["no-such-user"] = "The given user does not exist on the server"; ["no-such-host"] = "The given hostname does not exist in the config"; ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help"; ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see http://prosody.im/doc/prosodyctl for more info"; ["no-such-method"] = "This module has no commands"; ["not-running"] = "Prosody is not running"; }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); hosts = prosody.hosts; local function make_host(hostname) return { type = "local", events = prosody.events, modules = {}, users = require "core.usermanager".new_null_provider(hostname) }; end for hostname, config in pairs(config.getconfig()) do hosts[hostname] = make_host(hostname); end local modulemanager = require "core.modulemanager" local prosodyctl = require "util.prosodyctl" require "socket" ----------------------- -- FIXME: Duplicate code waiting for util.startup function read_version() -- Try to determine version local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version"); if version_file then prosody.version = version_file:read("*a"):gsub("%s*$", ""); version_file:close(); if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then prosody.version = "hg:"..prosody.version; end else prosody.version = "unknown"; end end local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; local show_usage = prosodyctl.show_usage; local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass; local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; local prosodyctl_timeout = (config.get("*", "prosodyctl_timeout") or 5) * 2; ----------------------- local commands = {}; local command = arg[1]; function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); return 1; end local user, host = arg[1]:match("([^@]+)@(.+)"); if not user and host then show_message [[Failed to understand JID, please supply the JID you want to create]] show_usage [[adduser user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) show_warning("The user will not be able to log in until this is changed."); hosts[host] = make_host(host); end if prosodyctl.user_exists{ user = user, host = host } then show_message [[That user already exists]]; return 1; end local password = read_password(); if not password then return 1; end local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; if ok then return 0; end show_message(msg) return 1; end function commands.passwd(arg) if not arg[1] or arg[1] == "--help" then show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]); return 1; end local user, host = arg[1]:match("([^@]+)@(.+)"); if not user and host then show_message [[Failed to understand JID, please supply the JID you want to set the password for]] show_usage [[passwd user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) show_warning("The user will not be able to log in until this is changed."); hosts[host] = make_host(host); end if not prosodyctl.user_exists { user = user, host = host } then show_message [[That user does not exist, use prosodyctl adduser to create a new user]] return 1; end local password = read_password(); if not password then return 1; end local ok, msg = prosodyctl.passwd { user = user, host = host, password = password }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.deluser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]); return 1; end local user, host = arg[1]:match("([^@]+)@(.+)"); if not user and host then show_message [[Failed to understand JID, please supply the JID you want to set the password for]] show_usage [[passwd user@host]] return 1; end if not host then show_message [[Please specify a JID, including a host. e.g. alice@example.com]]; return 1; end if not hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) show_warning("The user will not be able to log in until this is changed."); hosts[host] = make_host(host); end if not prosodyctl.user_exists { user = user, host = host } then show_message [[That user does not exist on this server]] return 1; end local ok, msg = prosodyctl.deluser { user = user, host = host }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.start(arg) if arg[1] == "--help" then show_usage([[start]], [[Start Prosody]]); return 1; end local ok, ret = prosodyctl.isrunning(); if not ok then show_message(error_messages[ret]); return 1; end if ret then local ok, ret = prosodyctl.getpid(); if not ok then show_message("Couldn't get running Prosody's PID"); show_message(error_messages[ret]); return 1; end show_message("Prosody is already running with PID %s", ret or "(unknown)"); return 1; end local ok, ret = prosodyctl.start(); if ok then if config.get("*", "daemonize") ~= false then local i=1; while true do local ok, running = prosodyctl.isrunning(); if ok and running then break; elseif i == 5 then show_message("Still waiting..."); elseif i >= prosodyctl_timeout then show_message("Prosody is still not running. Please give it some time or check your log files for errors."); return 2; end socket.sleep(0.5); i = i + 1; end show_message("Started"); end return 0; end show_message("Failed to start Prosody"); show_message(error_messages[ret]) return 1; end function commands.status(arg) if arg[1] == "--help" then show_usage([[status]], [[Reports the running status of Prosody]]); return 1; end local ok, ret = prosodyctl.isrunning(); if not ok then show_message(error_messages[ret]); return 1; end if ret then local ok, ret = prosodyctl.getpid(); if not ok then show_message("Couldn't get running Prosody's PID"); show_message(error_messages[ret]); return 1; end show_message("Prosody is running with PID %s", ret or "(unknown)"); return 0; else show_message("Prosody is not running"); if not switched_user and current_uid ~= 0 then print("\nNote:") print(" You will also see this if prosodyctl is not running under"); print(" the same user account as Prosody. Try running as root (e.g. "); print(" with 'sudo' in front) to gain access to Prosody's real status."); end return 2 end return 1; end function commands.stop(arg) if arg[1] == "--help" then show_usage([[stop]], [[Stop a running Prosody server]]); return 1; end if not prosodyctl.isrunning() then show_message("Prosody is not running"); return 1; end local ok, ret = prosodyctl.stop(); if ok then local i=1; while true do local ok, running = prosodyctl.isrunning(); if ok and not running then break; elseif i == 5 then show_message("Still waiting..."); elseif i >= prosodyctl_timeout then show_message("Prosody is still running. Please give it some time or check your log files for errors."); return 2; end socket.sleep(0.5); i = i + 1; end show_message("Stopped"); return 0; end show_message(error_messages[ret]); return 1; end function commands.restart(arg) if arg[1] == "--help" then show_usage([[restart]], [[Restart a running Prosody server]]); return 1; end commands.stop(arg); return commands.start(arg); end function commands.about(arg) read_version(); if arg[1] == "--help" then show_usage([[about]], [[Show information about this Prosody installation]]); return 1; end local array = require "util.array"; local keys = require "util.iterators".keys; print("Prosody "..(prosody.version or "(unknown version)")); print(""); print("# Prosody directories"); print("Data directory: ", CFG_DATADIR or "./"); print("Plugin directory:", CFG_PLUGINDIR or "./"); print("Config directory:", CFG_CONFIGDIR or "./"); print("Source directory:", CFG_SOURCEDIR or "./"); print(""); print("# Lua environment"); print("Lua version: ", _G._VERSION); print(""); print("Lua module search paths:"); for path in package.path:gmatch("[^;]+") do print(" "..path); end print(""); print("Lua C module search paths:"); for path in package.cpath:gmatch("[^;]+") do print(" "..path); end print(""); local luarocks_status = (pcall(require, "luarocks.loader") and "Installed ("..(luarocks.cfg.program_version or "2.x+")..")") or (pcall(require, "luarocks.require") and "Installed (1.x)") or "Not installed"; print("LuaRocks: ", luarocks_status); print(""); print("# Lua module versions"); local module_versions, longest_name = {}, 8; for name, module in pairs(package.loaded) do if type(module) == "table" and rawget(module, "_VERSION") and name ~= "_G" and not name:match("%.") then if #name > longest_name then longest_name = #name; end module_versions[name] = module._VERSION; end end local sorted_keys = array.collect(keys(module_versions)):sort(); for _, name in ipairs(array.collect(keys(module_versions)):sort()) do print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]); end print(""); end function commands.reload(arg) if arg[1] == "--help" then show_usage([[reload]], [[Reload Prosody's configuration and re-open log files]]); return 1; end if not prosodyctl.isrunning() then show_message("Prosody is not running"); return 1; end local ok, ret = prosodyctl.reload(); if ok then show_message("Prosody log files re-opened and config file reloaded. You may need to reload modules for some changes to take effect."); return 0; end show_message(error_messages[ret]); return 1; end -- ejabberdctl compatibility function commands.register(arg) local user, host, password = unpack(arg); if (not (user and host)) or arg[1] == "--help" then if user ~= "--help" then if not user then show_message [[No username specified]] elseif not host then show_message [[Please specify which host you want to register the user on]]; end end show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password"); return 1; end if not password then password = read_password(); if not password then show_message [[Unable to register user with no password]]; return 1; end end local ok, msg = prosodyctl.adduser { user = user, host = host, password = password }; if ok then return 0; end show_message(error_messages[msg]) return 1; end function commands.unregister(arg) local user, host = unpack(arg); if (not (user and host)) or arg[1] == "--help" then if user ~= "--help" then if not user then show_message [[No username specified]] elseif not host then show_message [[Please specify which host you want to unregister the user from]]; end end show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server"); return 1; end local ok, msg = prosodyctl.deluser { user = user, host = host }; if ok then return 0; end show_message(error_messages[msg]) return 1; end local openssl; local lfs; local cert_commands = {}; local function ask_overwrite(filename) return lfs.attributes(filename) and not show_yesno("Overwrite "..filename .. "?"); end function cert_commands.config(arg) if #arg >= 1 and arg[1] ~= "--help" then local conf_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".cnf"; if ask_overwrite(conf_filename) then return nil, conf_filename; end local conf = openssl.config.new(); conf:from_prosody(hosts, config, arg); show_message("Please provide details to include in the certificate config file."); show_message("Leave the field empty to use the default value or '.' to exclude the field.") for i, k in ipairs(openssl._DN_order) do local v = conf.distinguished_name[k]; if v then local nv; if k == "commonName" then v = arg[1] elseif k == "emailAddress" then v = "xmpp@" .. arg[1]; elseif k == "countryName" then local tld = arg[1]:match"%.([a-z]+)$"; if tld and #tld == 2 and tld ~= "uk" then v = tld:upper(); end end nv = show_prompt(("%s (%s):"):format(k, nv or v)); nv = (not nv or nv == "") and v or nv; if nv:find"[\192-\252][\128-\191]+" then conf.req.string_mask = "utf8only" end conf.distinguished_name[k] = nv ~= "." and nv or nil; end end local conf_file = io.open(conf_filename, "w"); conf_file:write(conf:serialize()); conf_file:close(); print(""); show_message("Config written to " .. conf_filename); return nil, conf_filename; else show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") end end function cert_commands.key(arg) if #arg >= 1 and arg[1] ~= "--help" then local key_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".key"; if ask_overwrite(key_filename) then return nil, key_filename; end os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048); local old_umask = pposix.umask("0377"); if openssl.genrsa{out=key_filename, key_size} then os.execute(("chmod 400 '%s'"):format(key_filename)); show_message("Key written to ".. key_filename); pposix.umask(old_umask); return nil, key_filename; end show_message("There was a problem, see OpenSSL output"); else show_usage("cert key HOSTNAME ", "Generates a RSA key named HOSTNAME.key\n " .."Prompts for a key size if none given") end end function cert_commands.request(arg) if #arg >= 1 and arg[1] ~= "--help" then local req_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".req"; if ask_overwrite(req_filename) then return nil, req_filename; end local _, key_filename = cert_commands.key({arg[1]}); local _, conf_filename = cert_commands.config(arg); if openssl.req{new=true, key=key_filename, utf8=true, config=conf_filename, out=req_filename} then show_message("Certificate request written to ".. req_filename); else show_message("There was a problem, see OpenSSL output"); end else show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") end end function cert_commands.generate(arg) if #arg >= 1 and arg[1] ~= "--help" then local cert_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".crt"; if ask_overwrite(cert_filename) then return nil, cert_filename; end local _, key_filename = cert_commands.key({arg[1]}); local _, conf_filename = cert_commands.config(arg); local ret; if key_filename and conf_filename and cert_filename and openssl.req{new=true, x509=true, nodes=true, key=key_filename, days=365, sha1=true, utf8=true, config=conf_filename, out=cert_filename} then show_message("Certificate written to ".. cert_filename); else show_message("There was a problem, see OpenSSL output"); end else show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") end end function commands.cert(arg) if #arg >= 1 and arg[1] ~= "--help" then openssl = require "util.openssl"; lfs = require "lfs"; local subcmd = table.remove(arg, 1); if type(cert_commands[subcmd]) == "function" then if not arg[1] then show_message"You need to supply at least one hostname" arg = { "--help" }; end if arg[1] ~= "--help" and not hosts[arg[1]] then show_message(error_messages["no-such-host"]); return end return cert_commands[subcmd](arg); end end show_usage("cert config|request|generate|key", "Helpers for generating X.509 certificates and keys.") end --------------------- if command and command:match("^mod_") then -- Is a command in a module local module_name = command:match("^mod_(.+)"); local ret, err = modulemanager.load("*", module_name); if not ret then show_message("Failed to load module '"..module_name.."': "..err); os.exit(1); end table.remove(arg, 1); local module = modulemanager.get_module("*", module_name); if not module then show_message("Failed to load module '"..module_name.."': Unknown error"); os.exit(1); end if not modulemanager.module_has_method(module, "command") then show_message("Fail: mod_"..module_name.." does not support any commands"); os.exit(1); end local ok, ret = modulemanager.call_module_method(module, "command", arg); if ok then if type(ret) == "number" then os.exit(ret); elseif type(ret) == "string" then show_message(ret); end os.exit(0); -- :) else show_message("Failed to execute command: "..error_messages[ret]); os.exit(1); -- :( end end if not commands[command] then -- Show help for all commands function show_usage(usage, desc) print(" "..usage); print(" "..desc); end print("prosodyctl - Manage a Prosody server"); print(""); print("Usage: "..arg[0].." COMMAND [OPTIONS]"); print(""); print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about" }; local done = {}; for _, command_name in ipairs(commands_order) do local command = commands[command_name]; if command then command{ "--help" }; print"" done[command_name] = true; end end for command_name, command in pairs(commands) do if not done[command_name] and not hidden_commands:contains(command_name) then command{ "--help" }; print"" done[command_name] = true; end end os.exit(0); end os.exit(commands[command]({ select(2, unpack(arg)) }));