#!/usr/bin/env lua
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
local socket = softreq "socket";
-- Defaults --
default_port = 5222;
default_namespace = "jabber:client";
default_timeout = 1.5;
-- Print debug output --
function debug() end -- Keep debug output quiet by default
-- Command-line flag handlers --
local flags = {};
function flags.d() -- Show debug
debug = print;
end
function flags.s() -- Use s2s
-- Use s2s
default_port = 5269;
default_namespace = "jabber:server";
end
function flags.t(arg)
default_timeout = tonumber(arg[1] or 1);
return 1;
end
function flags.help()
print("XMPP Server Fingerprinter");
print("(C) 2010 Matthew Wild. Licensed under the MIT/X11 License");
print("");
print("Syntax: "..arg[0].." [OPTIONS] ADDRESS [PORT]");
print("");
print("ADDRESS must be the network address or IP to connect to, SRV lookup");
print("is not supported.");
print("");
print("PORT is the port number to connect to. Defaults to 5222, or 5269 for");
print("server-to-server testing.");
print("");
print("Options:");
print(" -s:", "Server-to-server port testing (less reliable)");
print("-t NUM:", "Set timeout to NUM seconds");
print(" -d:", "Enable debug output (useful for adding new fingerprints)");
print("");
end
-- Process flags --
local function process_args(arg)
local f = arg[1] and arg[1]:match("^%-%-?(%S+)");
if f then
table.remove(arg, 1);
if flags[f] then
local used = flags[f](arg);
if used then
for i=1,used do
table.remove(arg, 1);
end
end
end
return process_args(arg);
end
end
if #arg > 0 then
process_args(arg);
else
flags.help();
os.exit(1);
end
if #arg < 1 then
io.stderr:write("ERROR: No server address specified.\n");
os.exit(1);
end
local received_cache = {};
local function _test(server, port, question, answer)
local received = received_cache[question];
if not received then
debug("||", "Connecting to "..server..":"..port.."...");
local conn = assert(socket.connect(server, port), "Failed to connect to "..arg[1]);
conn:send(question);
debug(">>", tostring(question));
conn:settimeout(default_timeout);
local data, err, partial = conn:receive("*a");
received = data or partial or "";
conn:close();
received_cache[question] = received;
if received ~= "" then
debug("<<", received);
else
debug("!!", tostring(err));
end
end
if received == "" then
return answer == false
elseif answer then
return received:match(answer);
end
end
function test(question, answer)
return _test(arg[1], tonumber(arg[2]) or default_port, question, answer);
end
function server_comment(comment)
table.insert(getfenv(2).server_comments, comment);
end
function literal(s)
return s:gsub("%p", "%%%1");
end
-----
q_invalid_xml = "abc\n";
q_empty_message_tag = "<message/>";
q_invalid_host = "<stream:stream xmlns='"..default_namespace.."' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='abc' to='0.0.0.0'>";
-----
testers = {};
dofile "servers/ejabberd.lua"
dofile "servers/google_talk.lua"
dofile "servers/isode_mlink.lua"
dofile "servers/jabberd14.lua"
dofile "servers/jabberd2.lua"
dofile "servers/prosody.lua"
dofile "servers/tigase.lua"
dofile "servers/djabberd.lua"
dofile "servers/openfire.lua"
dofile "servers/jabber_xcp.lua"
dofile "servers/soapbox.lua"
dofile "servers/facebook.lua"
function test_fingerprint(server)
local tester = testers[server];
local env = setmetatable({server_comments = {}}, { __index = _G });
setfenv(tester, env);
local ok, err = pcall(tester);
if not ok then
print("Error while testing for "..server..": "..err);
end
return env;
end
------------------------------------------------------------------
local results = {};
for server in pairs(testers) do
local res = test_fingerprint(server);
if res.server_name then
table.insert(results, res);
end
end
if #results > 1 then
print("Note... multiple matches, could be any:");
end
for _, result in ipairs(results) do
io.write(result.server_name);
if result.server_min_version then
io.write(", version "..result.server_min_version.." or later");
elseif result.server_version then
io.write(", version "..result.server_version);
end
io.write("\n");
if #result.server_comments > 0 then
print(" Notes:\n", table.concat(result.server_comments, "\n "));
end
end