");
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.lua 0000644 0001750 0001750 00000004570 12213321667 020346 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000006056 12213321667 017767 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000032072 12213321667 020501 0 ustar matthew matthew
--[[
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.lua 0000644 0001750 0001750 00000011364 12213321667 022330 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004024 12213321667 020525 0 ustar matthew matthew module: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.lua 0000644 0001750 0001750 00000001616 12213321667 017113 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000006403 12213321667 020303 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000103315 12213321667 020620 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000034246 12213321667 017767 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000023502 12213321667 020000 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000010727 12213321667 020322 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000002735 12213321667 017633 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000031653 12213321667 017462 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000002752 12213321667 017463 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000001761 12213321667 017122 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000013326 12213321667 017321 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000075402 12213321667 020410 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000011060 12213321667 017127 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 015670 5 ustar matthew matthew prosody-0.9.1/plugins/adhoc/adhoc.lib.lua 0000644 0001750 0001750 00000005431 12213321667 020217 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007230 12213321667 020310 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000025436 12213321667 020166 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000035520 12213321667 017634 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000014420 12213321667 017705 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003157 12213321667 017602 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003167 12213321667 017260 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 015376 5 ustar matthew matthew prosody-0.9.1/plugins/muc/muc.lib.lua 0000644 0001750 0001750 00000126476 12213321667 017450 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000017622 12213321667 017532 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 016256 5 ustar matthew matthew prosody-0.9.1/plugins/storage/xep227store.lib.lua 0000644 0001750 0001750 00000010416 12213321667 021632 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000005742 12213321667 021335 0 ustar matthew matthew
-- 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.lua 0000644 0001750 0001750 00000010017 12213321667 020724 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000022277 12213321667 016653 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000002014 12213321667 022073 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 013704 5 ustar matthew matthew prosody-0.9.1/man/prosodyctl.man 0000644 0001750 0001750 00000005620 12213321667 016604 0 ustar matthew matthew .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/ 0000775 0001750 0001750 00000000000 12213321667 014106 5 ustar matthew matthew prosody-0.9.1/util/dataforms.lua 0000644 0001750 0001750 00000014271 12213321667 016574 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000001726 12213321667 015673 0 ustar matthew matthew local function new_simple_form(form, result_handler)
return function(self, data, state)
if state then
if data.action == "cancel" then
return { status = "canceled" };
end
local fields, err = form:data(data.form);
return result_handler(fields, err, data);
else
return { status = "executing", actions = {"next", "complete", default = "complete"}, form = form }, "executing";
end
end
end
local function new_initial_data_form(form, initial_data, result_handler)
return function(self, data, state)
if state then
if data.action == "cancel" then
return { status = "canceled" };
end
local fields, err = form:data(data.form);
return result_handler(fields, err, data);
else
return { status = "executing", actions = {"next", "complete", default = "complete"},
form = { layout = form, values = initial_data() } }, "executing";
end
end
end
return { new_simple_form = new_simple_form,
new_initial_data_form = new_initial_data_form };
prosody-0.9.1/util/sasl.lua 0000644 0001750 0001750 00000007502 12213321667 015555 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004017 12213321667 015537 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000013377 12213321667 015710 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003225 12213321667 017276 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012126 12213321667 015221 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000005424 12213321667 016427 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000013331 12213321667 017012 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000000746 12213321667 016130 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000002676 12213321667 016416 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000001701 12213321667 016453 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000014360 12213321667 017002 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000005552 12213321667 017174 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004601 12213321667 017465 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004361 12213321667 016255 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012011 12213321667 017230 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000006202 12213321667 016623 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007010 12213321667 016747 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000005452 12213321667 015410 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003676 12213321667 016127 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007250 12213321667 015710 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000023706 12213321667 015570 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000025413 12213321667 016114 0 ustar matthew matthew -- 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, ""..name..">");
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, "")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
local tag_format = top_tag_format.."%s"..getstring(style_punc, "")..getstring(style_tagname, "%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.lua 0000644 0001750 0001750 00000003370 12213321667 015571 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012241 12213321667 017007 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000010445 12213321667 016276 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000003470 12213321667 015733 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000002437 12213321667 015415 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000003206 12213321667 016067 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000006543 12213321667 015735 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000000607 12213321667 015522 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000002361 12213321667 015557 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000022655 12213321667 016121 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000023013 12213321667 015405 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000025712 12213321667 017062 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000001434 12213321667 016411 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000001441 12213321667 016237 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003462 12213321667 016264 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 015050 5 ustar matthew matthew prosody-0.9.1/util/sasl/digest-md5.lua 0000644 0001750 0001750 00000022206 12213321667 017515 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007152 12213321667 016661 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004367 12213321667 017613 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000022437 12213321667 016666 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000014670 12213321667 015324 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000005335 12213321667 015363 0 ustar matthew matthew -- 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/TODO 0000644 0001750 0001750 00000000112 12213321667 013611 0 ustar matthew matthew == 1.0 ==
- Roster providers
- Statistics
- Clustering
- World domination
prosody-0.9.1/README 0000644 0001750 0001750 00000002120 12213321667 014002 0 ustar matthew matthew # 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/ 0000775 0001750 0001750 00000000000 12213321667 014271 5 ustar matthew matthew prosody-0.9.1/tools/erlparse.lua 0000644 0001750 0001750 00000010650 12213321667 016611 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000005647 12213321667 020317 0 ustar matthew matthew #!/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.lua 0000755 0001750 0001750 00000022624 12213321667 020243 0 ustar matthew matthew #!/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/ 0000775 0001750 0001750 00000000000 12213321667 016262 5 ustar matthew matthew prosody-0.9.1/tools/migration/migrator.cfg.lua 0000644 0001750 0001750 00000000533 12213321667 021346 0 ustar matthew matthew local 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/ 0000775 0001750 0001750 00000000000 12213321667 020106 5 ustar matthew matthew prosody-0.9.1/tools/migration/migrator/jabberd14.lua 0000644 0001750 0001750 00000010612 12213321667 022345 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000010636 12213321667 023476 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000002015 12213321667 022122 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000014320 12213321667 023165 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000006706 12213321667 022315 0 ustar matthew matthew #!/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/Makefile 0000644 0001750 0001750 00000002527 12213321667 017726 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000075211 12213321667 020760 0 ustar matthew matthew #!/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.lua 0000755 0001750 0001750 00000021361 12213321667 020012 0 ustar matthew matthew #!/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.lua 0000644 0001750 0001750 00000021740 12213321667 020756 0 ustar matthew matthew #!/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/ 0000775 0001750 0001750 00000000000 12213321667 015053 5 ustar matthew matthew prosody-0.9.1/fallbacks/lxp.lua 0000644 0001750 0001750 00000007065 12213321667 016367 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000023603 12213321667 016336 0 ustar matthew matthew -- 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/configure 0000755 0001750 0001750 00000020733 12213321667 015043 0 ustar matthew matthew #!/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/ 0000775 0001750 0001750 00000000000 12213321667 013676 5 ustar matthew matthew prosody-0.9.1/doc/stanza.txt 0000644 0001750 0001750 00000001046 12213321667 015736 0 ustar matthew matthew
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.txt 0000644 0001750 0001750 00000002252 12213321667 015541 0 ustar matthew matthew lxmppd - ...
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.txt 0000644 0001750 0001750 00000001636 12213321667 017126 0 ustar matthew matthew This 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.txt 0000644 0001750 0001750 00000001131 12213321667 017317 0 ustar matthew matthew
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.txt 0000644 0001750 0001750 00000001525 12213321667 017507 0 ustar matthew matthew No '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.txt 0000644 0001750 0001750 00000003211 12213321667 016115 0 ustar matthew matthew
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/ 0000775 0001750 0001750 00000000000 12213321667 013717 5 ustar matthew matthew prosody-0.9.1/net/server_select.lua 0000644 0001750 0001750 00000066324 12213321667 017300 0 ustar matthew matthew --
-- 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.lua 0000644 0001750 0001750 00000073041 12213321667 017134 0 ustar matthew matthew --[[
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.lua 0000644 0001750 0001750 00000004706 12213321667 015735 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 014676 5 ustar matthew matthew prosody-0.9.1/net/http/server.lua 0000644 0001750 0001750 00000022022 12213321667 016703 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000003745 12213321667 016505 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000012474 12213321667 016703 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000006034 12213321667 015350 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000071214 12213321667 015211 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012044 12213321667 015400 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000000657 12213321667 017316 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000000626 12213321667 016632 0 ustar matthew matthew -- 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/COPYING 0000644 0001750 0001750 00000002113 12213321667 014157 0 ustar matthew matthew Copyright (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.dist 0000644 0001750 0001750 00000016776 12213321667 017052 0 ustar matthew matthew -- 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/HACKERS 0000644 0001750 0001750 00000000726 12213321667 014137 0 ustar matthew matthew Welcome 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/prosody 0000755 0001750 0001750 00000027445 12213321667 014570 0 ustar matthew matthew #!/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/ 0000775 0001750 0001750 00000000000 12213321667 014673 5 ustar matthew matthew prosody-0.9.1/util-src/make.bat 0000644 0001750 0001750 00000000041 12213321667 016271 0 ustar matthew matthew @nmake /nologo /f Makefile.win %* prosody-0.9.1/util-src/net.c 0000644 0001750 0001750 00000005174 12213321667 015632 0 ustar matthew matthew /* 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.c 0000644 0001750 0001750 00000013155 12213321667 016315 0 ustar matthew matthew /* 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.c 0000644 0001750 0001750 00000022122 12213321667 016311 0 ustar matthew matthew /*
* 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/Makefile 0000644 0001750 0001750 00000001510 12213321667 016326 0 ustar matthew matthew
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.c 0000644 0001750 0001750 00000022722 12213321667 017013 0 ustar matthew matthew /* 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.win 0000644 0001750 0001750 00000002501 12213321667 017123 0 ustar matthew matthew
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.c 0000644 0001750 0001750 00000035625 12213321667 016372 0 ustar matthew matthew /* 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.c 0000644 0001750 0001750 00000004672 12213321667 016540 0 ustar matthew matthew /* 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/Makefile 0000644 0001750 0001750 00000004561 12213321667 014575 0 ustar matthew matthew
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/INSTALL 0000644 0001750 0001750 00000004504 12213321667 014163 0 ustar matthew matthew (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/ 0000775 0001750 0001750 00000000000 12213321667 014273 5 ustar matthew matthew prosody-0.9.1/tests/run_tests.bat 0000644 0001750 0001750 00000000213 12213321667 017003 0 ustar matthew matthew @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.lua 0000644 0001750 0001750 00000002353 12213321667 021222 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000003276 12213321667 017670 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000016113 12213321667 015755 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 015250 5 ustar matthew matthew prosody-0.9.1/tests/util/logger.lua 0000644 0001750 0001750 00000002462 12213321667 017234 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004630 12213321667 021666 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000003272 12213321667 021236 0 ustar matthew matthew -- 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.sh 0000755 0001750 0001750 00000000056 12213321667 016657 0 ustar matthew matthew #!/bin/sh
rm reports/*.report
lua test.lua $*
prosody-0.9.1/tests/test_core_s2smanager.lua 0000644 0001750 0001750 00000002503 12213321667 021105 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000004513 12213321667 023454 0 ustar matthew matthew package.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.lua 0000644 0001750 0001750 00000002103 12213321667 020364 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 015771 5 ustar matthew matthew prosody-0.9.1/tests/reports/empty 0000644 0001750 0001750 00000000050 12213321667 017043 0 ustar matthew matthew This file was intentionally left blank.
prosody-0.9.1/tests/test_core_configmanager.lua 0000644 0001750 0001750 00000003151 12213321667 021643 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000023744 12213321667 021755 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000006745 12213321667 017652 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000001731 12213321667 016777 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007263 12213321667 020175 0 ustar matthew matthew -- 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/ 0000775 0001750 0001750 00000000000 12213321667 014251 5 ustar matthew matthew prosody-0.9.1/certs/localhost.key 0000644 0001750 0001750 00000001567 12213321667 016762 0 ustar matthew matthew -----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.cnf 0000644 0001750 0001750 00000002737 12213321667 016433 0 ustar matthew matthew oid_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/Makefile 0000644 0001750 0001750 00000001637 12213321667 015716 0 ustar matthew matthew .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.crt 0000644 0001750 0001750 00000002452 12213321667 016754 0 ustar matthew matthew -----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.release 0000664 0001750 0001750 00000000006 12213321667 016166 0 ustar matthew matthew 0.9.1
prosody-0.9.1/core/ 0000775 0001750 0001750 00000000000 12213321667 014061 5 ustar matthew matthew prosody-0.9.1/core/loggingmanager.lua 0000644 0001750 0001750 00000016670 12213321667 017555 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012124 12213321667 017052 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000015707 12213321667 017612 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000007560 12213321667 017571 0 ustar matthew matthew
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.lua 0000644 0001750 0001750 00000006212 12213321667 016625 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000023571 12213321667 017412 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000020531 12213321667 017102 0 ustar matthew matthew local 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.lua 0000644 0001750 0001750 00000016525 12213321667 017373 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000024767 12213321667 016561 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000011175 12213321667 017100 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000012145 12213321667 017075 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000023432 12213321667 017437 0 ustar matthew matthew -- 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.lua 0000644 0001750 0001750 00000021541 12213321667 017465 0 ustar matthew matthew -- 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/AUTHORS 0000644 0001750 0001750 00000000567 12213321667 014207 0 ustar matthew matthew
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/DEPENDS 0000644 0001750 0001750 00000000376 12213321667 014142 0 ustar matthew matthew
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/prosodyctl 0000755 0001750 0001750 00000062112 12213321667 015261 0 ustar matthew matthew #!/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)) }));